OSDN Git Service

- Consistent propagation of start of week.
[tjqt4port/tj2qt4.git] / taskjuggler / Project.cpp
1 /*
2  * Project.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
14 #include <config.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <qdom.h>
18 #include <qdict.h>
19
20 #include "debug.h"
21 #include "Project.h"
22 #include "Utility.h"
23 #include "kotrus.h"
24
25 int Project::debugLevel = 0;
26 int Project::debugMode = -1;
27
28 Project::Project()
29 {
30         taskList.setAutoDelete(TRUE);
31         resourceList.setAutoDelete(TRUE);
32         accountList.setAutoDelete(TRUE);
33         activeAsap.setSorting(CoreAttributesList::PrioDown, 0);
34         activeAlap.setSorting(CoreAttributesList::PrioDown, 0);
35         priority = 500;
36         /* The following settings are country and culture dependent. Those
37          * defaults are probably true for many Western countries, but have to be
38          * changed in project files. */
39         dailyWorkingHours = 8.0;
40         yearlyWorkingDays = 252;
41         scheduleGranularity = ONEHOUR;
42         start = 0;
43         end = 0;
44         now = time(0);
45         weekStartsMonday = FALSE;
46         copyright = "";
47         minEffort = 0.0;
48         maxEffort = 1.0;
49         rate = 0.0;
50         currency = "";
51         currencyDigits = 3;
52         kotrus = 0;
53         xmlreport = 0;
54 #ifdef HAVE_ICAL
55 #ifdef HAVE_KDE
56         icalReport = 0;
57 #endif
58 #endif
59         
60         /* Initialize working hours with default values that match the Monday -
61          * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
62          * countries. */
63         // Sunday
64         workingHours[0] = new QPtrList<Interval>();
65         workingHours[0]->setAutoDelete(TRUE);
66
67         // Monday
68         workingHours[1] = new QPtrList<Interval>();
69         workingHours[1]->setAutoDelete(TRUE);
70         workingHours[1]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
71         workingHours[1]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
72         // Tuesday
73         workingHours[2] = new QPtrList<Interval>();
74         workingHours[2]->setAutoDelete(TRUE);
75         workingHours[2]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
76         workingHours[2]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
77         // Wednesday
78         workingHours[3] = new QPtrList<Interval>();
79         workingHours[3]->setAutoDelete(TRUE);
80         workingHours[3]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
81         workingHours[3]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
82         // Thursday
83         workingHours[4] = new QPtrList<Interval>();
84         workingHours[4]->setAutoDelete(TRUE);
85         workingHours[4]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
86         workingHours[4]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
87         // Friday
88         workingHours[5] = new QPtrList<Interval>();
89         workingHours[5]->setAutoDelete(TRUE);
90         workingHours[5]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
91         workingHours[5]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
92
93         // Saturday
94         workingHours[6] = new QPtrList<Interval>();
95         workingHours[6]->setAutoDelete(TRUE);
96 }
97
98 bool
99 Project::addId(const QString& id)
100 {
101         if (projectIDs.findIndex(id) != -1)
102                 return FALSE;
103         else
104                 projectIDs.append(id);
105         return TRUE;
106 }
107
108 QString
109 Project::getIdIndex(const QString& i) const
110 {
111         int idx;
112         if ((idx = projectIDs.findIndex(i)) == -1)
113                 return QString("?");
114         QString idxStr;
115         do
116         {
117                 idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
118                 idx /= 'Z' - 'A';
119         } while (idx > 'Z' - 'A');
120
121         return idxStr;
122 }
123
124 bool
125 Project::addTask(Task* t)
126 {
127         taskList.append(t);
128         return TRUE;
129 }
130
131 bool
132 Project::pass2()
133 {
134         QDict<Task> idHash;
135         bool error = FALSE;
136
137         // Generate sequence numbers for all lists.
138         taskList.createIndex(TRUE);
139         resourceList.createIndex(TRUE);
140         accountList.createIndex(TRUE);
141         shiftList.createIndex(TRUE);
142
143         // Initialize random generator.
144         srand((int) start);
145         
146         // Create hash to map task IDs to pointers.
147         for (Task* t = taskList.first(); t != 0; t = taskList.next())
148         {
149                 idHash.insert(t->getId(), t);
150         }
151         // Create cross links from dependency lists.
152         for (Task* t = taskList.first(); t != 0; t = taskList.next())
153         {
154                 if (!t->xRef(idHash))
155                         error = TRUE;
156         }
157         // Set dates according to implicit dependencies
158         for (Task* t = taskList.first(); t != 0; t = taskList.next())
159                 t->implicitXRef();
160
161         bool hasActualValues = FALSE;
162         for (Task* t = taskList.first(); t != 0; t = taskList.next())
163         {
164                 if (!t->preScheduleOk())
165                         error = TRUE;
166                 if (!hasActualValues && t->hasActualValues())
167                         hasActualValues = TRUE;
168         }
169
170         for (Task* t = taskList.first(); t != 0; t = taskList.next())
171                 if (t->loopDetector())
172                         return FALSE;
173
174         if (error)
175                 return FALSE;
176
177         if (DEBUGPS(1))
178                 qWarning("Scheduling plan scenario...");
179         preparePlan();
180         if (!schedule())
181                 error = TRUE;
182         finishPlan();
183
184         if (hasActualValues)
185         {
186                 if (DEBUGPS(1))
187                         qWarning("Scheduling actual scenario...");
188                 prepareActual();
189                 if (!schedule())
190                         error = TRUE;
191                 finishActual();
192         }
193
194         for (Task* t = taskList.first(); t != 0; t = taskList.next())
195                 t->computeBuffers();
196
197         /* Create indices for all lists according to their default sorting
198          * criteria. */
199         taskList.createIndex();
200         resourceList.createIndex();
201         accountList.createIndex();
202         shiftList.createIndex();
203         
204         return !error;
205 }
206
207 void
208 Project::preparePlan()
209 {
210         for (Task* t = taskList.first(); t != 0; t = taskList.next())
211                 t->preparePlan();
212         for (Task* t = taskList.first(); t != 0; t = taskList.next())
213                 t->propagateInitialValues();
214         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
215                 r->preparePlan();
216 }
217
218 void
219 Project::finishPlan()
220 {
221         for (Task* t = taskList.first(); t != 0; t = taskList.next())
222                 t->finishPlan();
223         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
224                 r->finishPlan();
225 }
226
227 void
228 Project::prepareActual()
229 {
230         for (Task* t = taskList.first(); t != 0; t = taskList.next())
231                 t->prepareActual();
232         for (Task* t = taskList.first(); t != 0; t = taskList.next())
233                 t->propagateInitialValues();
234         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
235                 r->prepareActual();
236 }
237
238 void
239 Project::finishActual()
240 {
241         for (Task* t = taskList.first(); t != 0; t = taskList.next())
242                 t->finishActual();
243         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
244                 r->finishActual();
245 }
246
247 bool
248 Project::schedule()
249 {
250         bool error = FALSE;
251
252         TaskList sortedTasks(taskList);
253         sortedTasks.setSorting(CoreAttributesList::PrioDown, 0);
254         sortedTasks.sort();
255
256         time_t timeDelta = scheduleGranularity;
257         bool done = FALSE;
258         time_t day;
259
260         /* Find all tasks than have enough information to be scheduled and sort
261          * them into two lists; one list for all ASAP tasks and one list for all
262          * ALAP tasks. */
263         updateActiveTaskList(sortedTasks);
264         for (day = start; !(activeAsap.isEmpty() && activeAlap.isEmpty()); )
265         {
266                 if (DEBUGPS(10))
267                         qWarning("Scheduling slot %s", time2ISO(day).latin1());
268                 timeDelta = scheduleGranularity;
269                 do
270                 {
271                         if (DEBUGPS(10))
272                                 qWarning("%d active ALAP tasks", activeAlap.count());
273                         done = TRUE;
274                         for (Task* t = activeAlap.first(); t != 0; t = activeAlap.next())
275                         {
276                                 if (!t->schedule(day, scheduleGranularity))
277                                 {
278                                         done = FALSE;
279                                         break;  // Start with top priority tasks again.
280                                 }
281                                 if (t->needsEarlierTimeSlot(day + scheduleGranularity))
282                                 {
283                                         if (DEBUGPS(5))
284                                                 qWarning("Scheduling backwards now");
285                                         timeDelta = -scheduleGranularity;
286                                 }
287                         }
288                 } while (!done);
289
290                 if (timeDelta >= 0)
291                 {
292                         do
293                         {
294                                 if (DEBUGPS(10))
295                                         qWarning("%d active ASAP tasks", activeAsap.count());
296                                 done = TRUE;
297                                 for (Task* t = activeAsap.first(); t != 0;
298                                          t = activeAsap.next())
299                                 {
300                                         if (!t->schedule(day, scheduleGranularity))
301                                         {
302                                                 done = FALSE;
303                                                 break;  // Start with top priority tasks again.
304                                         }
305                                 }
306                         } while (!done);
307                 }
308                 day += timeDelta;
309         
310                 /* Runaway detection */
311                 if (day < start)
312                 {
313                         if (DEBUGPS(2))
314                                 qWarning("Scheduler ran over start of project");
315                         
316                         for (Task* t = activeAlap.first(); t != 0; t = activeAlap.next())
317                                 if (t->setRunaway(day - timeDelta, scheduleGranularity))
318                                 {
319                                         if (DEBUGPS(5))
320                                                 qDebug("Marking task %s as runaway",
321                                                            t->getId().latin1());
322                                         error = TRUE;
323                                 }
324                         day = start;
325                 }
326                 if (day >= end)
327                 {
328                         if (DEBUGPS(2))
329                                 qDebug("Scheduler ran over end of project");
330                         
331                         for (Task* t = activeAsap.first(); t != 0; t = activeAsap.next())
332                                 if (t->setRunaway(day - timeDelta, scheduleGranularity))
333                                 {
334                                         if (DEBUGPS(5))
335                                                 qDebug("Marking task %s as runaway",
336                                                            t->getId().latin1());
337                                         error = TRUE;
338                                 }
339                         day = end - scheduleGranularity;
340                 }
341         }
342
343         if (error)
344                 for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
345                         if (t->isRunaway())
346                                 if (t->getScheduling() == Task::ASAP)
347                                         t->fatalError(QString(
348                                                 "End of task %1 does not fit into the project time "
349                                                 "frame.").arg(t->getId()));
350                                 else
351                                         t->fatalError(QString(
352                                                 "Start of task %1 does not fit into the project time "
353                                                 "frame.").arg(t->getId()));
354
355         if (!checkSchedule())
356                 error = TRUE;
357
358         return !error;
359 }
360
361 void
362 Project::updateActiveTaskList(TaskList& sortedTasks)
363 {
364         for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
365                 if (t->isActive())
366                         addActiveTask(t);
367 }
368
369 bool
370 Project::checkSchedule()
371 {
372         int errors = 0;
373         for (Task* t = taskList.first(); t != 0; t = taskList.next())
374         {
375                 /* Only check top-level tasks, since they recursively check their sub
376                  * tasks. */
377                 if (t->getParent() == 0)
378                         t->scheduleOk(errors);
379                 if (errors >= 10)
380                         break;
381         }
382
383         return errors == 0;
384 }
385
386 void
387 Project::setKotrus(Kotrus* k)
388 {
389         if (kotrus)
390                 delete kotrus;
391         kotrus = k;
392 }
393
394 void
395 Project::generateReports()
396 {
397         if (DEBUGPS(1))
398                 qWarning("Generating reports...");
399
400         // Generate task reports
401         for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
402                  h = htmlTaskReports.next())
403                 h->generate();
404
405         // Generate resource reports
406         for (HTMLResourceReport* r = htmlResourceReports.first(); r != 0;
407                  r = htmlResourceReports.next())
408                 r->generate();
409
410         // Generate account reports
411         for (HTMLAccountReport* r = htmlAccountReports.first(); r != 0;
412                  r = htmlAccountReports.next())
413                 r->generate();
414
415         // Generate calendar reports
416         for (HTMLWeeklyCalendar* r = htmlWeeklyCalendars.first(); r != 0;
417                  r = htmlWeeklyCalendars.next())
418                 r->generate();
419
420         // Generate export files
421         for (ExportReport* e = exportReports.first(); e != 0;
422                  e = exportReports.next())
423                 e->generate();
424
425         if( xmlreport )
426            xmlreport->generate();
427 #ifdef HAVE_ICAL
428 #ifdef HAVE_KDE
429         if( icalReport )
430            icalReport->generate();
431 #endif
432 #endif
433
434 }
435
436 bool
437 Project::needsActualDataForReports()
438 {
439         bool needsActual = FALSE;
440
441         // Generate task reports
442         for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
443                  h = htmlTaskReports.next())
444                 if (h->getShowActual())
445                         needsActual = TRUE;
446         // Generate resource reports
447         for (HTMLResourceReport* h = htmlResourceReports.first(); h != 0;
448                  h = htmlResourceReports.next())
449                 if (h->getShowActual())
450                         needsActual = TRUE;
451
452         // Generate account reports
453         for (HTMLAccountReport* h = htmlAccountReports.first(); h != 0;
454                  h = htmlAccountReports.next())
455                 if (h->getShowActual())
456                         needsActual = TRUE;
457
458         return needsActual;
459 }
460
461 void
462 Project::removeActiveTask(Task* t)
463 {
464         t->setScheduled();
465
466         if (DEBUGPS(5))
467                 qWarning("Deactivating %s", t->getId().latin1());
468
469         if (t->getScheduling() == Task::ASAP)
470                 activeAsap.removeRef(t);
471         else
472                 activeAlap.removeRef(t);
473 }
474
475 void
476 Project::addActiveTask(Task* t)
477 {
478         if (t->getScheduling() == Task::ASAP)
479         {
480                 if (activeAsap.findRef(t) == -1)
481                 {
482                         if (DEBUGPS(5))
483                                 qWarning("Activating %s", t->getId().latin1());
484                         activeAsap.inSort(t);
485                 }
486         }
487         else
488         {
489                 if (activeAlap.findRef(t) == -1)
490                 {
491                         if (DEBUGPS(5))
492                                 qWarning("Activating %s", t->getId().latin1()); 
493                         activeAlap.inSort(t);
494                 }
495         }
496 }
497
498 bool
499 Project::readKotrus()
500 {
501         if (!kotrus)
502                 return TRUE;
503                 
504         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
505                 r->dbLoadBookings(r->getKotrusId(), 0);
506
507         return TRUE;
508 }
509
510 bool
511 Project::updateKotrus()
512 {
513         return TRUE;
514 }
515
516
517 bool
518 Project::loadFromXML( const QString& inpFile )
519 {
520    QDomDocument doc;
521    QFile file( inpFile );
522
523    doc.setContent( &file );
524    qDebug(  "Loading XML " + inpFile );
525
526    QDomElement elemProject = doc.documentElement();
527
528    if( !elemProject.isNull())
529    {
530       parseDomElem( elemProject );
531    }
532    else
533    {
534       qDebug("Empty !" );
535    }
536    pass2();
537    return true;
538 }
539
540
541 void Project::parseDomElem( QDomElement& parentElem )
542 {
543    QDomElement elem = parentElem.firstChild().toElement();
544
545    for( ; !elem.isNull(); elem = elem.nextSibling().toElement() )
546    {
547       QString tagName = elem.tagName();
548       
549       qDebug(  "|| elemType: " + tagName );
550       
551       if( tagName == "Task" )
552       {
553          QString tId = elem.attribute("Id");
554          Task *t = new Task( this, tId, QString(), 0, QString(), 0 );
555
556          t->loadFromXML( elem, this  );
557          addTask( t );
558       }
559       else if( tagName == "Name" )
560       {
561          setName( elem.text() );
562       }
563       else if( tagName == "Version" )
564          setVersion( elem.text() );
565       else if( tagName == "Priority" )
566          setPriority( elem.text().toInt());
567       else if( tagName == "start" )
568          setStart( elem.text().toLong());
569       else if( tagName == "end" )
570          setEnd( elem.text().toLong());
571                
572       // parseDomElem( elem );
573    }
574 }