OSDN Git Service

* Support for later completion of task and resources added. By
[tjqt4port/tj2qt4.git] / taskjuggler / Task.cpp
1 /*
2  * task.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of version 2 of the GNU General Public License as
8  * published by the Free Software Foundation.
9  *
10  * $Id$
11  */
12
13 /* -- DTD --
14  <!-- Task element, child of projects and for subtasks -->
15  <!ELEMENT Task         (Id, Name, ParentTask*, Note*,
16                          minStart, maxStart,
17                          minEnd, maxEnd,
18                          actualStart, actualEnd,
19                          planStart, planEnd,
20                          SubTasks*, Depends*, Previous*, Followers*,
21                          Allocations*, bookedResources*, note*)>
22  <!ATTLIST Task         ProjectID CDATA #IMPLIED
23                         complete  CDATA #REQUIRED
24                         Priority  CDATA #IMPLIED
25                         Type (Task|Milestone) #REQUIRED>
26                         
27  <!ELEMENT ParentTask   (#PCDATA)>
28  <!ELEMENT Id           (#PCDATA)>
29  <!ELEMENT Name         (#PCDATA)>
30  <!ELEMENT Note         (#PCDATA)>
31  <!ELEMENT ProjectID    (#PCDATA)>
32  <!ELEMENT TaskType     (#PCDATA)>
33  <!ELEMENT Priority     (#PCDATA)>
34  <!ELEMENT start        (#PCDATA)>
35  <!ELEMENT end          (#PCDATA)>
36  <!ELEMENT minStart     (#PCDATA)>
37  <!ELEMENT maxStart     (#PCDATA)>
38  <!ELEMENT minEnd       (#PCDATA)>
39  <!ELEMENT maxEnd       (#PCDATA)>
40  <!ELEMENT actualStart  (#PCDATA)>
41  <!ELEMENT actualEnd    (#PCDATA)>
42  <!ELEMENT planStart    (#PCDATA)>
43  <!ELEMENT planEnd      (#PCDATA)>
44  <!ELEMENT SubTasks     (Task+)>
45  <!ELEMENT Depends      (TaskID+)>
46  <!ELEMENT TaskID       (#PCDATA)>
47  <!ELEMENT Previous     (TaskID+)>
48  <!ELEMENT Followers    (TaskID+)>
49  <!ELEMENT Allocations  (Allocation+)>
50  <!ELEMENT Allocation   EMPTY>
51  <!ELEMENT bookedResources (ResourceID+)>
52  <!ELEMENT ResourceID   (#PCDATA)>
53  <!ELEMENT note         (#PCDATA)>
54
55  <!ATTLIST ResourceID
56            Name CDATA #REQUIRED>
57  <!ATTLIST Allocation
58            load CDATA #REQUIRED
59            ResourceID CDATA #REQUIRED>
60            
61  <!-- Date values contain human readable date -->
62  <!ATTLIST minStart
63            humanReadable CDATA #REQUIRED>
64  <!ATTLIST maxStart
65            humanReadable CDATA #REQUIRED>
66  <!ATTLIST minEnd
67            humanReadable CDATA #REQUIRED>
68  <!ATTLIST maxEnd
69            humanReadable CDATA #REQUIRED>
70  <!ATTLIST actualStart
71            humanReadable CDATA #REQUIRED>
72  <!ATTLIST actualEnd
73            humanReadable CDATA #REQUIRED>
74  <!ATTLIST planStart
75            humanReadable CDATA #REQUIRED>
76  <!ATTLIST planEnd
77            humanReadable CDATA #REQUIRED>
78    /-- DTD --/
79 */
80  
81 #include <stdio.h>
82
83 #include "Task.h"
84 #include "Project.h"
85
86 int Task::debugLevel = 0;
87
88 Task*
89 TaskList::getTask(const QString& id)
90 {
91         for (Task* t = first(); t != 0; t = next())
92                 if (t->getId() == id)
93                         return t;
94
95         return 0;
96 }
97
98 Task::Task(Project* proj, const QString& id_, const QString& n, Task* p,
99                    const QString f, int l)
100         : CoreAttributes(proj, id_, n, p), file(f), line(l)
101 {
102         allocations.setAutoDelete(TRUE);
103
104         scheduling = ASAP;
105         milestone = FALSE;
106         complete = -1;
107         note = "";
108         account = 0;
109         startCredit = endCredit = 0.0;
110         lastSlot = 0;
111         schedulingDone = FALSE;
112         responsible = 0;
113
114         if (p)
115         {
116                 // Inherit flags from parent task.
117                 for (QStringList::Iterator it = p->flags.begin();
118                          it != p->flags.end(); ++it)
119                         addFlag(*it);
120
121                 // Set attributes that are inherited from parent task.
122                 projectId = p->projectId;
123                 priority = p->priority;
124                 minStart = p->minStart;
125                 maxStart = p->maxEnd;
126                 minEnd = p->minStart; 
127                 maxEnd = p->maxEnd;
128                 responsible = p->responsible;
129                 account = p->account;
130         }
131         else
132         {
133                 // Set attributes that are inherited from global attributes.
134                 projectId = proj->getCurrentId();
135                 priority = proj->getPriority();
136                 minStart = minEnd = proj->getStart();
137                 maxStart = maxEnd = proj->getEnd();
138         }
139
140         planStart = planEnd = 0;
141         planDuration = planLength = planEffort = 0.0;
142
143         actualStart = actualEnd = 0;
144         actualDuration = actualLength = actualEffort = 0.0;
145
146         start = end = 0;
147         duration = length = effort = 0.0;
148 }
149
150 void
151 Task::fatalError(const QString& msg) const
152 {
153         qWarning("%s:%d:%s\n", file.latin1(), line, msg.latin1());
154 }
155
156 bool
157 Task::schedule(time_t& date, time_t slotDuration)
158 {
159         // Task is already scheduled.
160         if (schedulingDone)
161         {
162                 qFatal("Task %s is already scheduled", id.latin1());
163                 return TRUE;
164         }
165
166         bool limitChanged = FALSE;
167         if (start == 0 &&
168                 (scheduling == Task::ASAP ||
169                  (length == 0.0 && duration == 0.0 && effort == 0.0 && !milestone)))
170         {
171                 /* No start time has been specified. The start time is either
172                  * start time of the parent (or the project start time if the
173                  * tasks has no previous tasks) or the start time is
174                  * determined by the end date of the last previous task. */
175                 time_t es;
176                 if (depends.count() == 0)
177                 {
178                         if (parent == 0)
179                                 start = project->getStart();
180                         else if (getParent()->start != 0)
181                                 start = getParent()->start;
182                         else
183                                 return TRUE;
184                         propagateStart();
185                 }
186                 else if ((es = earliestStart()) > 0)
187                 {
188                         start = es;
189                         propagateStart();
190                 }
191                 else
192                         return TRUE;    // Task cannot be scheduled yet.
193
194                 limitChanged = TRUE;
195         }
196
197         if (end == 0 &&
198                 (scheduling == Task::ALAP ||
199                  (length == 0.0 && duration == 0.0 && effort == 0.0 && !milestone)))
200         {       
201                 /* No end time has been specified. The end time is either end
202                  * time of the parent (or the project end time if the tasks
203                  * has no previous tasks) or the end time is determined by the
204                  * start date of the earliest following task. */
205                 time_t le;
206                 if (preceeds.count() == 0)
207                 {
208                         if (parent == 0)
209                                 end = project->getEnd();
210                         else if (getParent()->end != 0)
211                                 end = getParent()->end;
212                         else
213                                 return TRUE;
214                         propagateEnd();
215                 }
216                 else if ((le = latestEnd()) > 0)
217                 {
218                         end = le;
219                         propagateEnd();
220                 }
221                 else                    return TRUE;    // Task cannot be scheduled yet.
222                 
223                 limitChanged = TRUE;
224         }
225
226         if (scheduling == Task::ASAP)
227         {
228                 if (lastSlot == 0)
229                 {
230                         lastSlot = start - 1;
231                         doneEffort = 0.0;
232                         doneDuration = 0.0;
233                         doneLength = 0.0;
234                         workStarted = FALSE;
235                         tentativeEnd = date + slotDuration - 1;
236                         if (debugLevel > 2)
237                                 qWarning("Scheduling of %s starts at %s (%s)",
238                                                  id.latin1(), time2ISO(lastSlot).latin1(),
239                                                  time2ISO(date).latin1());
240                 }
241                 /* Do not schedule anything if the time slot is not directly
242                  * following the time slot that was previously scheduled. */
243                 if (!((date - slotDuration <= lastSlot) && (lastSlot < date)))
244                         return !limitChanged;
245                 lastSlot = date + slotDuration - 1;
246         }
247         else
248         {
249                 if (lastSlot == 0)
250                 {
251                         lastSlot = end + 1;
252                         doneEffort = 0.0;
253                         doneDuration = 0.0;
254                         doneLength = 0.0;
255                         workStarted = FALSE;
256                         tentativeStart = date;
257                         if (debugLevel > 2)
258                                 qWarning("Scheduling of ALAP task %s starts at %s (%s)",
259                                                  id.latin1(), time2ISO(lastSlot).latin1(),
260                                                  time2ISO(date).latin1());
261                 }
262                 /* Do not schedule anything if the current time slot is not
263                  * directly preceeding the previously scheduled time slot. */
264                 if (!((date + slotDuration <= lastSlot) &&
265                         (lastSlot < date + 2 * slotDuration)))
266                         return !limitChanged;
267                 lastSlot = date;
268         }
269
270         if (debugLevel > 3)
271                 qWarning("Scheduling %s at %s",
272                                  id.latin1(), time2ISO(date).latin1());
273
274         if ((duration > 0.0) || (length > 0.0))
275         {
276                 /* Length specifies the number of working days (as daily load)
277                  * and duration specifies the number of calender days. */
278                 if (!allocations.isEmpty())
279                         bookResources(date, slotDuration);
280
281                 doneDuration += ((double) slotDuration) / ONEDAY;
282                 if (!(isWeekend(date) || project->isVacation(date)))
283                         doneLength += ((double) slotDuration) / ONEDAY;
284
285                 if (debugLevel > 4)
286                         qWarning("Length: %f/%f   Duration: %f/%f",
287                                          doneLength, length,
288                                          doneDuration, duration);
289                 // Check whether we are done with this task.
290                 if ((length > 0.0 && doneLength >= length * 0.999999) ||
291                         (duration > 0.0 && doneDuration >= duration * 0.999999))
292                 {
293                         if (scheduling == ASAP)
294                         {
295                                 if (doneEffort > 0.0)
296                                 {
297                                         end = tentativeEnd;
298                                         date = end - slotDuration + 1;
299                                 }
300                                 else
301                                         end = date + slotDuration - 1;
302                                 propagateEnd();
303                         }
304                         else
305                         {
306                                 if (doneEffort > 0.0)
307                                 {
308                                         start = tentativeStart;
309                                         date = start;
310                                 }
311                                 else
312                                         start = date;
313                                 propagateStart();
314                         }
315                         project->removeActiveTask(this);
316                         return FALSE;
317                 }
318         }
319         else if (effort > 0.0)
320         {
321                 /* The effort of the task has been specified. We have to look
322                  * how much the resources can contribute over the following
323                  * workings days until we have reached the specified
324                  * effort. */
325 //              if (project->isVacation(date))
326 //                      return TRUE;
327                 bookResources(date, slotDuration);
328                 // Check whether we are done with this task.
329                 if (doneEffort >= effort)
330                 {
331                         if (scheduling == ASAP)
332                         {
333                                 end = tentativeEnd;
334                                 propagateEnd();
335                         }
336                         else
337                         {
338                                 start = tentativeStart;
339                                 propagateStart();
340                         }
341                         project->removeActiveTask(this);
342                         return FALSE;
343                 }
344         }
345         else if (milestone)
346         {
347                 // Task is a milestone.
348                 if (scheduling == ASAP)
349                 {
350                         end = start;
351                         propagateEnd();
352                 }
353                 else
354                 {
355                         start = end;
356                         propagateStart();
357                 }
358                 project->removeActiveTask(this);
359                 return FALSE;
360         }
361         else if (start != 0 && end != 0)
362         {
363                 // Task with start and end date but no duration criteria.
364                 if (!allocations.isEmpty() && !project->isVacation(date))
365                         bookResources(date, slotDuration);
366
367                 if ((scheduling == ASAP && (date + slotDuration) >= end) ||
368                         (scheduling == ALAP && date <= start))
369                 {
370                         project->removeActiveTask(this);
371                         return FALSE;
372                 }
373         }
374
375         return TRUE;
376 }
377
378 bool
379 Task::scheduleContainer(bool safeMode)
380 {
381         if (schedulingDone)
382                 return TRUE;
383
384         Task* t;
385         time_t nstart = 0;
386         time_t nend = 0;
387
388         // Check that this is really a container task
389         if ((t = subFirst()))
390         {
391                 if (t->start == 0 || t->end == 0)
392                         return TRUE;
393                 nstart = t->start;
394                 nend = t->end;
395         }
396         else
397                 return TRUE;
398
399         for (t = subNext() ; t != 0; t = subNext())
400         {
401                 /* Make sure that all sub tasks have been scheduled. If not we
402                  * can't yet schedule this task. */
403                 if (t->start == 0 || t->end == 0)
404                         return TRUE;
405
406                 if (t->start < nstart)
407                         nstart = t->start;
408                 if (t->end > nend)
409                         nend = t->end;
410         }
411
412         if (start == 0)
413         {
414                 start = nstart;
415                 propagateStart(safeMode);
416         }
417         if (end == 0)
418         {
419                 end = nend;
420                 propagateEnd(safeMode);
421         }
422
423         schedulingDone = TRUE;
424
425         return FALSE;
426 }
427
428 void
429 Task::propagateStart(bool safeMode)
430 {
431         if (start == 0)
432                 return;
433
434         if (debugLevel > 1)
435                 qWarning("PS1: Setting start of %s to %s",
436                                  id.latin1(), time2ISO(start).latin1());
437
438         for (Task* t = previous.first(); t != 0; t = previous.next())
439                 if (t->end == 0 && t->scheduling == ALAP &&
440                         t->latestEnd() != 0)
441                 {
442                         t->end = t->latestEnd();
443                         if (debugLevel > 1)
444                                 qWarning("PS2: Setting end of %s to %s",
445                                                  t->id.latin1(), time2ISO(t->end).latin1());
446                         t->propagateEnd(safeMode);
447                         if (safeMode && t->isActive())
448                                 project->addActiveTask(t);
449                 }
450
451         /* Propagate start time to sub tasks which have only an implicit
452          * dependancy on the parent task. Do not touch container tasks. */
453         for (Task* t = subFirst(); t != 0; t = subNext())
454         {
455                 if (t->start == 0 && t->previous.isEmpty() &&
456                         t->sub.isEmpty() && t->scheduling == ASAP)
457                 {
458                         t->start = start;
459                         if (debugLevel > 1)
460                                 qWarning("PS3: Setting start of %s to %s",
461                                                  t->id.latin1(), time2ISO(t->start).latin1());   
462                         if (safeMode && t->isActive())
463                                 project->addActiveTask(t);
464                         t->propagateStart(safeMode);
465                 }
466         }
467
468         if (safeMode && parent)
469                 getParent()->scheduleContainer(TRUE);
470 }
471
472 void
473 Task::propagateEnd(bool safeMode)
474 {
475         if (end == 0)
476                 return;
477
478         if (debugLevel > 1)
479                 qWarning("PE1: Setting end of %s to %s",
480                                  id.latin1(), time2ISO(end).latin1());
481
482         for (Task* t = followers.first(); t != 0; t = followers.next())
483                 if (t->start == 0 && t->scheduling == ASAP &&
484                         t->earliestStart() != 0)
485                 {
486                         t->start = t->earliestStart();
487                         if (debugLevel > 1)
488                                 qWarning("PE2: Setting start of %s to %s",
489                                                  t->id.latin1(), time2ISO(t->start).latin1());
490                         t->propagateStart(safeMode);
491                         if (safeMode && t->isActive())
492                                 project->addActiveTask(t);
493                 }
494         /* Propagate end time to sub tasks which have only an implicit
495          * dependancy on the parent task. Do not touch container tasks. */
496         for (Task* t = subFirst(); t != 0; t = subNext())
497                 if (t->end == 0 && t->followers.isEmpty() &&
498                         t->sub.isEmpty() && t->scheduling == ALAP)
499                 {
500                         t->end = end;
501                         if (debugLevel > 1)
502                                 qWarning("PE3: Setting end of %s to %s",
503                                                  t->id.latin1(), time2ISO(t->end).latin1());
504                         if (safeMode && t->isActive())
505                                 project->addActiveTask(t);
506                         t->propagateEnd(safeMode);
507                 }
508
509         if (safeMode && parent)
510                 getParent()->scheduleContainer(TRUE);
511 }
512
513 void
514 Task::propagateInitialValues()
515 {
516         if (start != 0)
517                 propagateStart(FALSE);
518         if (end != 0)
519                 propagateEnd(FALSE);
520         // Check if the some data of sub tasks can already be propagated.
521         if (subFirst())
522                 scheduleContainer(TRUE);
523 }
524
525 bool
526 Task::bookResources(time_t date, time_t slotDuration)
527 {
528         bool allocFound = FALSE;
529
530         for (Allocation* a = allocations.first();
531                  a != 0 && (effort == 0.0 || doneEffort < effort);
532                  a = allocations.next())
533         {
534                 if (a->isPersistent() && a->getLockedResource())
535                 {       
536                         bookResource(a->getLockedResource(), date, slotDuration,
537                                                  a->getLoad());
538                 }
539                 else if (bookResource(a->getResource(), date, slotDuration,
540                                                           a->getLoad()))
541                 {
542                         allocFound = TRUE;
543                         if (a->isPersistent())
544                                 a->setLockedResource(a->getResource());
545                 }
546                 else
547                 {
548                         /* TODO: Try to free the main resource from a lower
549                          * priority task. */
550                         for (Resource* r = a->first(); r != 0; r = a->next())
551                                 if (bookResource(r, date, slotDuration, a->getLoad()))
552                                 {
553                                         allocFound = TRUE;
554                                         if (a->isPersistent())
555                                                 a->setLockedResource(r);
556                                         break;
557                                 }
558                 }
559         }
560         return allocFound;
561 }
562
563 bool
564 Task::bookResource(Resource* r, time_t date, time_t slotDuration,
565                                    int loadFactor)
566 {
567         bool booked = FALSE;
568         double intervalLoad = project->convertToDailyLoad(slotDuration);
569
570         for (Resource* rit = r->subResourcesFirst(); rit != 0;
571                  rit = r->subResourcesNext())
572         {
573                 if ((*rit).isAvailable(date, slotDuration, loadFactor, this))
574                 {
575                         (*rit).book(new Booking(
576                                 Interval(date, date + slotDuration - 1), this,
577                                 account ? account->getKotrusId() : QString(""),
578                                 projectId));
579                         addBookedResource(rit);
580
581                         /* Move the start date to make sure that there is
582                          * some work going on on the start date. */
583                         if (!workStarted)
584                         {
585                                 if (scheduling == ASAP)
586                                         start = date;
587                                 else if (scheduling == ALAP)
588                                         end = date + slotDuration - 1;
589                                 else
590                                         qFatal("Unknown scheduling mode");
591                                 workStarted = TRUE;
592                         }
593
594                         tentativeStart = date;
595                         tentativeEnd = date + slotDuration - 1;
596                         doneEffort += intervalLoad * (*rit).getEfficiency();
597
598                         booked = TRUE;
599                 }
600         }
601         return booked;
602 }
603
604 bool
605 Task::needsEarlierTimeSlot(time_t date)
606 {
607         if (scheduling == ALAP && lastSlot > 0 && !schedulingDone &&
608                 date > lastSlot && sub.isEmpty())
609                 return TRUE;
610         if (scheduling == ASAP && lastSlot > 0 && !schedulingDone &&
611                 date > lastSlot + 1 && sub.isEmpty())
612                 return TRUE;
613
614         return FALSE;
615 }
616
617 bool
618 Task::isCompleted(time_t date) const
619 {
620         if (complete != -1)
621         {
622                 // some completion degree was specified.
623                 return ((complete / 100.0) *
624                                 (actualEnd - actualStart) + actualStart) > date;
625         }
626         
627
628         return (project->getNow() > date);
629 }
630
631 time_t
632 Task::earliestStart()
633 {
634         time_t date = 0;
635         for (Task* t = depends.first(); t != 0; t = depends.next())
636         {
637                 // All tasks this task depends on must have an end date set.
638                 if (t->end == 0)
639                         return 0;
640                 // Milestones are assumed to have duration 0.
641                 if (t->end > date)
642                         date = t->end + (t->milestone ? 0 : 1);
643         }
644
645         return date;
646 }
647
648 time_t
649 Task::latestEnd()
650 {
651         time_t date = 0;
652         for (Task* t = preceeds.first(); t != 0; t = preceeds.next())
653         {
654                 // All tasks this task preceeds must have an start date set.
655                 if (t->start == 0)
656                         return 0;
657                 if (date == 0 || t->start < date)
658                         date = t->start - 1;
659         }
660
661         return date;
662 }
663
664 double
665 Task::getPlanCalcDuration() const
666 {
667         time_t delta = planEnd - planStart;
668         if (delta < ONEDAY)
669                 return (project->convertToDailyLoad(delta));
670         else
671                 return (double) delta / ONEDAY;
672 }
673
674 double
675 Task::getPlanLoad(const Interval& period, Resource* resource)
676 {
677         double load = 0.0;
678
679         if (subFirst())
680         {
681                 for (Task* t = subFirst(); t != 0; t = subNext())
682                         load += t->getPlanLoad(period, resource);
683         }
684
685         if (resource)
686                 load += resource->getPlanLoad(period, this);
687         else
688                 for (Resource* r = planBookedResources.first(); r != 0;
689                          r = planBookedResources.next())
690                         load += r->getPlanLoad(period, this);
691
692         return load;
693 }
694
695 double
696 Task::getActualCalcDuration() const
697 {
698         time_t delta = actualEnd - actualStart;
699         if (delta < ONEDAY)
700                 return (project->convertToDailyLoad(delta));
701         else
702                 return (double) delta / ONEDAY;
703 }
704
705 double
706 Task::getActualLoad(const Interval& period, Resource* resource)
707 {
708         double load = 0.0;
709
710         if (subFirst())
711         {
712                 for (Task* t = subFirst(); t != 0; t = subNext())
713                         load += t->getActualLoad(period, resource);
714         }
715         
716         if (resource)
717                 load += resource->getActualLoad(period, this);
718         else
719                 for (Resource* r = actualBookedResources.first(); r != 0;
720                          r = actualBookedResources.next())
721                         load += r->getActualLoad(period, this);
722
723         return load;
724 }
725
726 double
727 Task::getPlanCredits(const Interval& period, Resource* resource,
728                                          bool recursive)
729 {
730         double credits = 0.0;
731
732         if (recursive && subFirst())
733         {
734                 for (Task* t = subFirst(); t != 0; t = subNext())
735                         credits += t->getPlanCredits(period, resource, recursive);
736         }
737
738         if (resource)
739                 credits += resource->getPlanCredits(period, this);
740         else
741                 for (Resource* r = planBookedResources.first(); r != 0;
742                          r = planBookedResources.next())
743                         credits += r->getPlanCredits(period, this);
744
745         if (period.contains(planStart))
746                 credits += startCredit;
747         if (period.contains(planEnd))
748                 credits += endCredit;
749
750         return credits;
751 }
752
753 double
754 Task::getActualCredits(const Interval& period, Resource* resource,
755                                            bool recursive)
756 {
757         double credits = 0.0;
758
759         if (recursive && subFirst())
760         {
761                 for (Task* t = subFirst(); t != 0; t = subNext())
762                         credits += t->getActualCredits(period, resource, recursive);
763         }
764
765         if (resource)
766                 credits += resource->getActualCredits(period, this);
767         else
768                 for (Resource* r = actualBookedResources.first(); r != 0;
769                          r = actualBookedResources.next())
770                         credits += r->getActualCredits(period, this);
771
772         if (period.contains(actualStart))
773                 credits += startCredit;
774         if (period.contains(actualEnd))
775                 credits += endCredit;
776
777         return credits;
778 }
779
780 bool
781 Task::xRef(QDict<Task>& hash)
782 {
783         bool error = FALSE;
784
785         for (QStringList::Iterator it = dependsIds.begin();
786                  it != dependsIds.end(); ++it)
787         {
788                 QString absId = resolveId(*it);
789                 Task* t;
790                 if ((t = hash.find(absId)) == 0)
791                 {
792                         fatalError(QString("Unknown dependency '") + absId + "'");
793                         error = TRUE;
794                 }
795                 else if (depends.find(t) != -1)
796                 {
797                         fatalError(QString("No need to specify dependency '") + absId +
798                                                            "' twice.");
799                         error = TRUE;
800                 }
801                 else
802                 {
803                         depends.append(t);
804                         previous.append(t);
805                         t->followers.append(this);
806                 }
807         }
808
809         for (QStringList::Iterator it = preceedsIds.begin();
810                  it != preceedsIds.end(); ++it)
811         {
812                 QString absId = resolveId(*it);
813                 Task* t;
814                 if ((t = hash.find(absId)) == 0)
815                 {
816                         fatalError(QString("Unknown dependency '") + absId + "'");
817                         error = TRUE;
818                 }
819                 else if (preceeds.find(t) != -1)
820                 {
821                         fatalError(QString("No need to specify dependency '") + absId +
822                                                            "' twice.");
823                         error = TRUE;
824                 }
825                 else
826                 {
827                         preceeds.append(t);
828                         followers.append(t);
829                         t->previous.append(this);
830                 }
831         }
832
833         return !error;
834 }
835
836 QString
837 Task::resolveId(QString relId)
838 {
839         /* Converts a relative ID to an absolute ID. Relative IDs start
840          * with a number of bangs. A set of bangs means 'Name of the n-th
841          * parent task' with n being the number of bangs. */
842         if (relId[0] != '!')
843                 return relId;
844
845         Task* t = this;
846         unsigned int i;
847         for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
848         {
849                 if (!t->parent)
850                 {
851                         fatalError(QString("Illegal relative ID '") + relId + "'");
852                         return relId;
853                 }
854                 t = t->getParent();
855         }
856         if (t)
857                 return t->id + "." + relId.right(relId.length() - i);
858         else
859                 return relId.right(relId.length() - i);
860 }
861
862 bool
863 Task::preScheduleOk()
864 {
865         if ((planEffort > 0 || actualEffort > 0) && allocations.count() == 0)
866         {
867                 fatalError(QString().sprintf(
868                         "No allocations specified for effort based task %s",
869                         id.latin1()));
870                 return FALSE;
871         }
872
873         // Check plan values.
874         int durationSpec = 0;
875         if (planEffort > 0.0)
876                 durationSpec++;
877         if (planLength > 0.0)
878                 durationSpec++;
879         if (planDuration > 0.0)
880                 durationSpec++;
881
882         int limitSpec = 0;
883         if (planStart != 0 || !depends.isEmpty())
884                 limitSpec++;
885         if (planEnd != 0 || !preceeds.isEmpty())
886                 limitSpec++;
887
888         if (durationSpec > 1)
889         {
890                 fatalError(QString().sprintf("In task %s:", id.latin1()) +
891                         "You can specify either a length, a duration or an effort.");
892                 return FALSE;
893         }
894         else if (durationSpec == 1)
895         {
896                 if (milestone)
897                 {
898                         fatalError(QString().sprintf("In task %s:", id.latin1()) +
899                                            "You cannot specify a duration criteria for a "
900                                            "milestone.");
901                         return FALSE;
902                 }
903                 if (limitSpec == 2)
904                 {
905                         fatalError(QString().sprintf("In task %s:", id.latin1()) +
906                                            "You cannot specify a duration criteria together with "
907                                            "a start and end criteria.");
908                         return FALSE;
909                 }
910         }
911         else if (limitSpec != 2 &&
912                          !(limitSpec == 1 && milestone) &&
913                          !(limitSpec <= 1 && !sub.isEmpty()))
914         {
915                 fatalError(QString().sprintf("In task %s:", id.latin1()) +
916                                    "If you do not specify a plan duration criteria "
917                                    "you have to specify a start and end criteria.");
918                 return FALSE;
919         }
920
921         // Check actual values
922         durationSpec = 0;
923         if (actualEffort > 0.0 || planEffort > 0.0)
924                 durationSpec++;
925         if (actualLength > 0.0 || planLength > 0.0)
926                 durationSpec++;
927         if (actualDuration > 0.0 || planDuration > 0.0)
928                 durationSpec++;
929
930         limitSpec = 0;
931         if (planStart != 0 || actualStart != 0 || !depends.isEmpty())
932                 limitSpec++;
933         if (planEnd != 0 || actualEnd != 0 || !preceeds.isEmpty())
934                 limitSpec++;
935
936         if (durationSpec > 1)
937         {
938                 fatalError(QString().sprintf("In task %s:", id.latin1()) +
939                         "You can specify either an actual length, duration or effort.");
940                 return FALSE;
941         }
942         else if (durationSpec == 1)
943         {
944                 if (milestone)
945                 {
946                         fatalError(QString().sprintf("In task %s:", id.latin1()) +
947                                            "You cannot specify an actual duration criteria for a "
948                                            "milestone.");
949                         return FALSE;
950                 }
951                 if (limitSpec == 2)
952                 {
953                         fatalError(QString().sprintf("In task %s:", id.latin1()) +
954                                            "You cannot specify an actual duration criteria "
955                                            "together with a start and end criteria.");
956                         return FALSE;
957                 }
958         }
959         else if (limitSpec != 2 &&
960                          !(limitSpec == 1 && milestone) &&
961                          !(limitSpec <= 1 && !sub.isEmpty()))
962         {
963                 fatalError(QString().sprintf("In task %s:", id.latin1()) +
964                                    "If you do not specify an actual duration criteria you "
965                                    "have to specify a start and end criteria.");
966                 return FALSE;
967         }
968
969         if (!sub.isEmpty())
970         {
971                 if (durationSpec > 0)
972                 {
973                         fatalError("A container tasks may never have a duration criteria");
974                         return FALSE;
975                 }
976                 if (!allocations.isEmpty())
977                 {
978                         fatalError("A container tasks may never have resource "
979                                            "allocations");
980                         return FALSE;
981                 }
982         }
983
984         double intervalLoad = project->convertToDailyLoad(project->getScheduleGranularity());
985
986         for (Allocation* a = allocations.first(); a != 0; a = allocations.next())
987         {
988                 if (a->getLoad() < intervalLoad * 100.0)
989                 {
990                         qWarning("Warning: Load is smaller than scheduling granularity "
991                                          "(Task: %s, Resource: %s). Minimal load is %.2f.",
992                                          id.latin1(), a->getResource()->getId().latin1(), intervalLoad + 0.005);
993                         a->setLoad((int) (intervalLoad * 100.0));
994                 }
995         }
996
997         return TRUE;
998 }
999
1000 bool
1001 Task::scheduleOk()
1002 {
1003         if (!sub.isEmpty())
1004         {
1005                 // All sub task must fit into their parent task.
1006                 for (Task* t = subFirst(); t != 0; t = subNext())
1007                 {
1008                         if (start > t->start)
1009                         {
1010                                 fatalError(QString().sprintf(
1011                                         "Task %s starts ealier than parent", t->id.latin1()));
1012                                 return FALSE;
1013                         }
1014                         if (end < t->end)
1015                         {
1016                                 fatalError(QString().sprintf(
1017                                         "Task %s ends later than parent", t->id.latin1()));
1018                                 return FALSE;
1019                         }
1020                 }
1021         }
1022
1023         if (start == 0)
1024         {
1025                 fatalError(QString("Task '") + id + "' has no start time.");
1026                 return FALSE;
1027         }
1028         if (minStart != 0 && start < minStart)
1029         {
1030                 fatalError(QString().sprintf(
1031                         "Start time %s of task %s is earlier than requested minimum %s",
1032                         time2ISO(start).latin1(), id.latin1(),
1033                         time2ISO(minStart).latin1()));
1034                 return FALSE;
1035         }
1036         if (minStart != 0 && start > maxStart)
1037         {
1038                 fatalError(QString().sprintf(
1039                         "Start time %s of task %s is later than requested maximum %s",
1040                         time2ISO(start).latin1(), id.latin1(),
1041                         time2ISO(maxStart).latin1()));
1042                 return FALSE;
1043         }
1044         if (start < project->getStart() || start > project->getEnd())
1045         {
1046                 fatalError(QString().sprintf(
1047                         "Start time %s of task %s is outside of project period",
1048                         time2ISO(start).latin1(), id.latin1()));
1049                 return FALSE;
1050         }
1051         if (end == 0)
1052         {
1053                 fatalError(QString("Task '") + id + "' has no end time.");
1054                 return FALSE;
1055         }
1056         if (minEnd != 0 && end < minEnd)
1057         {
1058                 fatalError(QString().sprintf(
1059                         "End time %s of task %s is earlier than requested minimum %s",
1060                         time2ISO(end).latin1(), id.latin1(), time2ISO(minEnd).latin1()));
1061                 return FALSE;
1062         }
1063         if (maxEnd != 0 && end > maxEnd)
1064         {
1065                 fatalError(QString().sprintf(
1066                         "End time %s of task %s is later than requested maximum %s",
1067                         time2ISO(end).latin1(), id.latin1(), time2ISO(maxEnd).latin1()));
1068                 return FALSE;
1069         }
1070         if (end < project->getStart() || end > project->getEnd())
1071         {
1072                 fatalError(QString().sprintf(
1073                         "End time %s of task %s is outside of project period",
1074                         time2ISO(end).latin1(), id.latin1()));
1075                 return FALSE;
1076         }
1077         // Check if all previous tasks end before start of this task.
1078         for (Task* t = previous.first(); t != 0; t = previous.next())
1079                 if (t->end > start)
1080                 {
1081                         fatalError(QString().sprintf(
1082                                 "Task %s ends at %s but needs to preceed task %s "
1083                                 "which starts at %s",
1084                                 t->id.latin1(), time2ISO(t->end).latin1(),
1085                                 id.latin1(), time2ISO(start).latin1()));
1086                         return FALSE;
1087                 }
1088         // Check if all following task start after this tasks end.
1089         for (Task* t = followers.first(); t != 0; t = followers.next())
1090                 if (end > t->start)
1091                 {
1092                         fatalError(QString().sprintf(
1093                                 "Task %s starts at %s but needs to follow task %s "
1094                                 "which ends at %s",
1095                                 t->id.latin1(), time2ISO(t->start).latin1(),
1096                                 id.latin1(), time2ISO(end).latin1()));
1097                         return FALSE;
1098                 }
1099
1100         if (!schedulingDone)
1101         {
1102                 fatalError(QString().sprintf(
1103                         "Task %s has not been marked completed. It is scheduled to last "
1104                         "from %s to %s. This might be a bug in the TaskJuggler "
1105                         "scheduler.", id.latin1(), time2ISO(start).latin1(),
1106                         time2ISO(end).latin1()));
1107                 return FALSE;
1108         }
1109
1110         return TRUE;
1111 }
1112
1113 bool
1114 Task::isActive()
1115 {
1116         if (schedulingDone || !sub.isEmpty())
1117                 return FALSE;
1118
1119         if ((scheduling == ASAP && start != 0) ||
1120                 (scheduling == ALAP && end != 0))
1121                 return TRUE;
1122
1123         return FALSE;
1124 }
1125
1126 bool
1127 Task::isPlanActive(const Interval& period) const
1128 {
1129         Interval work;
1130         if (isMilestone())
1131                 work = Interval(planStart, planStart + 1);
1132         else
1133                 work = Interval(planStart, planEnd);
1134         return period.overlaps(work);
1135 }
1136
1137 bool
1138 Task::isActualActive(const Interval& period) const
1139 {
1140         Interval work;
1141         if (isMilestone())
1142                 work = Interval(actualStart, actualStart + 1);
1143         else
1144                 work = Interval(actualStart, actualEnd);
1145         return period.overlaps(work);
1146 }
1147
1148 void
1149 Task::getSubTaskList(TaskList& tl)
1150 {
1151         for (Task* t = subFirst(); t != 0; t = subNext())
1152         {
1153                 tl.append(t);
1154                 t->getSubTaskList(tl);
1155         }
1156 }
1157
1158 bool
1159 Task::isSubTask(Task* tsk)
1160 {
1161         for (Task* t = subFirst(); t != 0; t = subNext())
1162                 if (t == tsk || t->isSubTask(tsk))
1163                         return TRUE;
1164
1165         return FALSE;
1166 }
1167
1168 void
1169 Task::treeSortKey(QString& key)
1170 {
1171         if (!parent)
1172         {
1173                 key = QString().sprintf("%06d", sequenceNo) + key;
1174                 return;
1175         }
1176
1177         int i = 1;
1178         for (Task* t = getParent()->subFirst(); t != 0;
1179                  t = getParent()->subNext(), i++)
1180                 if (t == this)
1181                 {
1182                         key = QString().sprintf("%06d", i) + key;
1183                         break;
1184                 }
1185         getParent()->treeSortKey(key);
1186 }
1187
1188 void
1189 Task::preparePlan()
1190 {
1191         start = planStart;
1192         end = planEnd;
1193
1194         duration = planDuration;
1195         length = planLength;
1196         effort = planEffort;
1197         lastSlot = 0;
1198         schedulingDone = FALSE;
1199         bookedResources.clear();
1200
1201         if (actualStart == 0.0)
1202                 actualStart = planStart;
1203         if (actualEnd == 0.0)
1204                 actualEnd = planEnd;
1205         if (actualDuration == 0.0)
1206                 actualDuration =  planDuration;
1207         if (actualLength == 0.0)
1208                 actualLength = planLength;
1209         if (actualEffort == 0.0)
1210                 actualEffort = planEffort;
1211 }
1212
1213 void
1214 Task::finishPlan()
1215 {
1216         planStart = start;
1217         planEnd = end;
1218         planDuration = doneDuration;
1219         planLength = doneLength;
1220         planEffort = doneEffort;
1221         planBookedResources = bookedResources;
1222 }
1223
1224 void
1225 Task::prepareActual()
1226 {
1227         start = actualStart;
1228         end = actualEnd;
1229
1230         duration = actualDuration;
1231         length = actualLength;
1232         effort = actualEffort;
1233         lastSlot = 0;
1234         schedulingDone = FALSE;
1235         bookedResources.clear();
1236 }
1237
1238 void
1239 Task::finishActual()
1240 {
1241         actualStart = start;
1242         actualEnd = end;
1243         actualDuration = doneDuration;
1244         actualLength = doneLength;
1245         actualEffort = doneEffort;
1246         actualBookedResources = bookedResources;
1247 }
1248
1249 QDomElement Task::xmlElement( QDomDocument& doc )
1250 {
1251    QDomElement taskElem = doc.createElement( "Task" );
1252    QDomElement tempElem;
1253    
1254    QDomText t;
1255    taskElem.appendChild( ReportXML::createXMLElem( doc, "Id", getId()));
1256    taskElem.appendChild( ReportXML::createXMLElem( doc, "Name",getName() ));
1257    CoreAttributes *parent = getParent();
1258    if( parent )
1259       taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
1260          
1261    if( !note.isEmpty())
1262       taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
1263    
1264    tempElem = ReportXML::createXMLElem( doc, "minStart", QString::number( minStart ));
1265    tempElem.setAttribute( "humanReadable", time2ISO( minStart ));
1266    taskElem.appendChild( tempElem );
1267    
1268    tempElem = ReportXML::createXMLElem( doc, "maxStart", QString::number( maxStart ));
1269    tempElem.setAttribute( "humanReadable", time2ISO( maxStart ));
1270    taskElem.appendChild( tempElem );
1271
1272    tempElem = ReportXML::createXMLElem( doc, "minEnd", QString::number( minEnd ));
1273    tempElem.setAttribute( "humanReadable", time2ISO( minEnd ));
1274    taskElem.appendChild( tempElem );
1275
1276    tempElem = ReportXML::createXMLElem( doc, "maxEnd", QString::number( maxEnd ));
1277    tempElem.setAttribute( "humanReadable", time2ISO( maxEnd ));
1278    taskElem.appendChild( tempElem );
1279    
1280    tempElem = ReportXML::createXMLElem( doc, "actualStart", QString::number( actualStart ));
1281    tempElem.setAttribute( "humanReadable", time2ISO( actualStart ));
1282    taskElem.appendChild( tempElem );
1283
1284    tempElem = ReportXML::createXMLElem( doc, "actualEnd", QString::number( actualEnd ));
1285    tempElem.setAttribute( "humanReadable", time2ISO( actualEnd ));
1286    taskElem.appendChild( tempElem );
1287    
1288    tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( planStart ));
1289    tempElem.setAttribute( "humanReadable", time2ISO( planStart ));
1290    taskElem.appendChild( tempElem );
1291
1292    tempElem = ReportXML::createXMLElem( doc, "planEnd", QString::number( planEnd ));
1293    tempElem.setAttribute( "humanReadable", time2ISO( planEnd ));
1294    taskElem.appendChild( tempElem );
1295
1296 #if 0
1297    taskElem.appendChild( ReportXML::createXMLElem( doc, "maxStart", QString::number( maxStart )));
1298    taskElem.appendChild( ReportXML::createXMLElem( doc, "minEnd", QString::number( minEnd )));
1299    taskElem.appendChild( ReportXML::createXMLElem( doc, "maxEnd", QString::number( maxEnd )));
1300    taskElem.appendChild( ReportXML::createXMLElem( doc, "actualStart", QString::number( actualStart )));
1301    taskElem.appendChild( ReportXML::createXMLElem( doc, "actualEnd", QString::number( actualEnd )));
1302    taskElem.appendChild( ReportXML::createXMLElem( doc, "planStart", QString::number( planStart )));
1303    taskElem.appendChild( ReportXML::createXMLElem( doc, "planEnd", QString::number( planEnd )));
1304 #endif
1305    taskElem.setAttribute( "ProjectID", projectId );
1306    taskElem.setAttribute( "Priority", getPriority() );
1307    taskElem.setAttribute( "complete", complete );
1308    taskElem.setAttribute( "Type", milestone ? "Milestone" : "Task" );
1309    
1310    /* Now start the subtasks */
1311    int cnt = 0;
1312    QDomElement subTaskElem = doc.createElement( "SubTasks" );
1313    for (Task* t = subFirst(); t != 0; t = subNext())
1314    {
1315       if( t != this )
1316       {
1317          QDomElement sTask = t->xmlElement( doc );
1318          subTaskElem.appendChild( sTask );
1319          cnt++;
1320       }
1321    }
1322    if( cnt > 0 )
1323       taskElem.appendChild( subTaskElem);
1324
1325    /* Tasks (by id) on which this task depends */
1326    if( dependsIds.count() > 0 )
1327    {
1328       QDomElement deps = doc.createElement( "Depends" );
1329       
1330       for (QValueListConstIterator<QString> it1= dependsIds.begin(); it1 != dependsIds.end(); ++it1)
1331       {
1332          deps.appendChild( ReportXML::createXMLElem( doc, "TaskID", *it1 ));
1333       }
1334       taskElem.appendChild( deps );
1335    }
1336
1337    /* list of tasks by id which are previous */
1338    if( previous.count() > 0 )
1339    {
1340       QDomElement prevs = doc.createElement( "Previous" );
1341
1342       TaskList tl( previous );
1343       for (Task* t = tl.first(); t != 0; t = tl.next())
1344       { 
1345          if( t != this )
1346          {
1347             prevs.appendChild( ReportXML::createXMLElem( doc, "TaskID", t->getId()));
1348          }
1349       }
1350       taskElem.appendChild( prevs );
1351    }
1352    
1353    /* list of tasks by id which follow */
1354    if( followers.count() > 0 )
1355    {
1356       QDomElement foll = doc.createElement( "Followers" );
1357
1358       TaskList tl( followers );
1359       for (Task* t = tl.first(); t != 0; t = tl.next())
1360       { 
1361          if( t != this )
1362          {
1363             foll.appendChild( ReportXML::createXMLElem( doc, "TaskID", t->getId()));
1364          }
1365       }
1366
1367       taskElem.appendChild( foll );
1368    }
1369
1370    /* Allocations */
1371    if( allocations.count() > 0 )
1372    {
1373       QDomElement alloc = doc.createElement( "Allocations" );
1374
1375       QPtrList<Allocation> al(allocations);
1376       for (Allocation* a = al.first(); a != 0; a = al.next())
1377       {
1378          alloc.appendChild( a->xmlElement( doc ));
1379       }
1380       taskElem.appendChild( alloc );
1381    }
1382
1383    /* booked Ressources */
1384    if( bookedResources.count() > 0 )
1385    {    
1386       QDomElement bres = doc.createElement( "bookedResources" );
1387
1388       QPtrList<Resource> br(bookedResources);
1389       for (Resource* r = br.first(); r != 0; r = br.next())
1390       {
1391          bres.appendChild( r->xmlIDElement( doc ));
1392       }
1393       taskElem.appendChild( bres );
1394    }
1395
1396    
1397    /* Comment */
1398    if( ! note.isEmpty())
1399    {
1400       QDomElement e = doc.createElement( "note" );
1401       taskElem.appendChild( e );
1402       t = doc.createTextNode( note );
1403       e.appendChild( t );
1404    }
1405
1406    return( taskElem );
1407 }
1408
1409 int
1410 TaskList::compareItems(QCollection::Item i1, QCollection::Item i2)
1411 {
1412         Task* t1 = static_cast<Task*>(i1);
1413         Task* t2 = static_cast<Task*>(i2);
1414
1415         switch (sorting)
1416         {
1417         case TreeMode:
1418         {
1419                 QString key1;
1420                 t1->treeSortKey(key1);
1421                 QString key2;
1422                 t2->treeSortKey(key2);
1423                 return key1 < key2 ? -1 : 1;
1424         }
1425         case StartUp:
1426                 return t1->start == t2->start ? 0 :
1427                         t1->start > t2->start ? -1 : 1;
1428         case StartDown:
1429                 return t1->start == t2->start ? 0 :
1430                         t1->start < t2->start ? -1 : 1;
1431         case EndUp:
1432                 return t1->end == t2->end ? 0 :
1433                         t1->end > t2->end ? -1 : 1;
1434         case EndDown:
1435                 return t1->end == t2->end ? 0 :
1436                         t1->end < t2->end ? -1 : 1;
1437         case PrioUp:
1438                 if (t1->priority == t2->priority)
1439                         return 0; // TODO: Use duration as next criteria
1440                 else
1441                         return (t1->priority - t2->priority);
1442         case PrioDown:
1443                 if (t1->priority == t2->priority)
1444                         return 0; // TODO: Use duration as next criteria
1445                 else
1446                         return (t2->priority - t1->priority);
1447         case ResponsibleUp:
1448         {
1449                 QString fn1;
1450                 t1->responsible->getFullName(fn1);
1451                 QString fn2;
1452                 t2->responsible->getFullName(fn2);
1453                 return - fn1.compare(fn2);
1454         }
1455         case ResponsibleDown:
1456         {
1457                 QString fn1;
1458                 t1->responsible->getFullName(fn1);
1459                 QString fn2;
1460                 t2->responsible->getFullName(fn2);
1461                 return fn1.compare(fn2);
1462         }
1463         default:
1464                 return CoreAttributesList::compareItems(i1, i2);
1465         }               
1466 }
1467
1468 #ifdef HAVE_KDE
1469
1470 void Task::toTodo( KCal::Todo* todo, KCal::CalendarLocal *cal )
1471 {
1472    if( !todo ) return;
1473    QDateTime dt;
1474
1475    // todo->setReadOnly( true );
1476    
1477    /* Start-Time of the task */
1478    dt.setTime_t( getPlanStart() );
1479    todo->setDtStart( dt );
1480    todo->setHasDueDate( true );
1481    
1482    /* Due-Time of the todo -> plan End  */
1483    dt.setTime_t( getPlanEnd());
1484    todo->setDtDue( dt );
1485    todo->setHasStartDate(true);
1486
1487    /* Description and summary -> project ID */
1488    todo->setDescription( getNote() );
1489    todo->setSummary( getName() );
1490    todo->setPriority( getPriority() );
1491    todo->setCompleted( getComplete() );
1492
1493    /* Resources */
1494    QPtrList<Resource> resList;
1495    resList = getPlanBookedResources();
1496    QStringList strList;
1497    
1498    Resource *res = 0;
1499    for ( res = resList.first(); res; res = resList.next() )
1500    {
1501       strList.append( res->getName());
1502    }
1503    todo->setResources(strList);
1504    
1505 }
1506
1507
1508 #endif