OSDN Git Service

20b92ffc9edfccc2d86683824b654295e0b08502
[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
34         vacationList.setAutoDelete(TRUE);
35         
36         htmlTaskReports.setAutoDelete(TRUE);
37         htmlResourceReports.setAutoDelete(TRUE);
38         htmlAccountReports.setAutoDelete(TRUE);
39         htmlWeeklyCalendars.setAutoDelete(TRUE);
40         exportReports.setAutoDelete(TRUE);
41
42         scenarioNames.append("Plan");
43         scenarioNames.append("Actual");
44
45         xmlreport = 0;
46 #ifdef HAVE_ICAL
47 #ifdef HAVE_KDE
48         icalReport = 0;
49 #endif
50 #endif
51
52         priority = 500;
53         /* The following settings are country and culture dependent. Those
54          * defaults are probably true for many Western countries, but have to be
55          * changed in project files. */
56         dailyWorkingHours = 8.0;
57         yearlyWorkingDays = 252;
58         scheduleGranularity = ONEHOUR;
59         weekStartsMonday = TRUE;
60         timeFormat = "%Y-%m-%d %H:%M";
61         shortTimeFormat = "%H:%M";
62
63         start = 0;
64         end = 0;
65         now = time(0);
66         
67         minEffort = 0.0;
68         maxEffort = 1.0;
69         rate = 0.0;
70         currencyDigits = 3;
71         kotrus = 0;
72         
73         /* Initialize working hours with default values that match the Monday -
74          * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
75          * countries. */
76         // Sunday
77         workingHours[0] = new QPtrList<Interval>();
78         workingHours[0]->setAutoDelete(TRUE);
79
80         for (int i = 1; i < 6; ++i)
81         {
82                 workingHours[i] = new QPtrList<Interval>();
83                 workingHours[i]->setAutoDelete(TRUE);
84                 workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
85                 workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
86         }
87
88         // Saturday
89         workingHours[6] = new QPtrList<Interval>();
90         workingHours[6]->setAutoDelete(TRUE);
91 }
92
93 Project::~Project()
94 {
95         delete xmlreport;
96 #ifdef HAVE_ICAL
97 #ifdef HAVE_KDE
98         delete icalReport;
99 #endif
100 #endif
101 }
102
103 const QString&
104 Project::getScenarioName(int sc)
105 {
106         return scenarioNames[sc];
107 }
108
109 bool
110 Project::addId(const QString& id)
111 {
112         if (projectIDs.findIndex(id) != -1)
113                 return FALSE;
114         else
115                 projectIDs.append(id);
116         return TRUE;
117 }
118
119 QString
120 Project::getIdIndex(const QString& i) const
121 {
122         int idx;
123         if ((idx = projectIDs.findIndex(i)) == -1)
124                 return QString("?");
125         QString idxStr;
126         do
127         {
128                 idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
129                 idx /= 'Z' - 'A';
130         } while (idx > 'Z' - 'A');
131
132         return idxStr;
133 }
134
135 bool
136 Project::pass2(bool checkOnlySyntax)
137 {
138         QDict<Task> idHash;
139         bool error = FALSE;
140
141         // Generate sequence numbers for all lists.
142         taskList.createIndex(TRUE);
143         resourceList.createIndex(TRUE);
144         accountList.createIndex(TRUE);
145         shiftList.createIndex(TRUE);
146
147         // Initialize random generator.
148         srand((int) start);
149         
150         // Create hash to map task IDs to pointers.
151         for (Task* t = taskList.first(); t != 0; t = taskList.next())
152         {
153                 idHash.insert(t->getId(), t);
154         }
155         // Create cross links from dependency lists.
156         for (Task* t = taskList.first(); t != 0; t = taskList.next())
157         {
158                 if (!t->xRef(idHash))
159                         error = TRUE;
160         }
161         // Set dates according to implicit dependencies
162         for (Task* t = taskList.first(); t != 0; t = taskList.next())
163                 t->implicitXRef();
164
165         // Find out what scenarios need to be scheduled.
166         // TODO: No multiple scenario support yet.
167         bool hasExtraValues = FALSE;
168         for (Task* t = taskList.first(); t != 0; t = taskList.next())
169                 if (!hasExtraValues && t->hasExtraValues(Task::Actual))
170                         hasExtraValues = TRUE;
171
172         /* Now we can copy the missing values from the plan scenario to the     other
173          * scenarios. */
174         for (int sc = 1; sc < getMaxScenarios(); sc++)
175                 overlayScenario(sc);
176
177         // Now check that all tasks have sufficient data to be scheduled.
178         for (Task* t = taskList.first(); t != 0; t = taskList.next())
179                 if (!t->preScheduleOk())
180                         error = TRUE;
181
182         for (Task* t = taskList.first(); t != 0; t = taskList.next())
183                 if (t->loopDetector())
184                         return FALSE;
185
186         if (error)
187                 return FALSE;
188
189         if (checkOnlySyntax)
190                 return TRUE;
191
192         if (DEBUGPS(1))
193                 qWarning("Scheduling plan scenario...");
194         prepareScenario(Task::Plan);
195         if (!schedule("Plan"))
196         {
197                 if (DEBUGPS(2))
198                         qWarning("Scheduling errors in plan scenario.");
199                 error = TRUE;
200         }
201         finishScenario(Task::Plan);
202
203         if (hasExtraValues)
204         {
205                 if (DEBUGPS(1))
206                         qWarning("Scheduling actual scenario...");
207                 prepareScenario(Task::Actual);
208                 if (!schedule("Actual"))
209                 {
210                         if (DEBUGPS(2))
211                                 qWarning("Scheduling errors in actual scenario.");
212                         error = TRUE;
213                 }
214                 finishScenario(Task::Actual);
215         }
216
217         for (Task* t = taskList.first(); t != 0; t = taskList.next())
218                 t->computeBuffers();
219
220         /* Create indices for all lists according to their default sorting
221          * criteria. */
222         taskList.createIndex();
223         resourceList.createIndex();
224         accountList.createIndex();
225         shiftList.createIndex();
226         
227         return !error;
228 }
229
230 void
231 Project::overlayScenario(int sc)
232 {
233         for (Task* t = taskList.first(); t != 0; t = taskList.next())
234                 t->overlayScenario(sc);
235 }
236
237 void
238 Project::prepareScenario(int sc)
239 {
240         for (Task* t = taskList.first(); t != 0; t = taskList.next())
241                 t->prepareScenario(sc);
242         for (Task* t = taskList.first(); t != 0; t = taskList.next())
243                 t->propagateInitialValues();
244         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
245                 r->prepareScenario(sc);
246 }
247
248 void
249 Project::finishScenario(int sc)
250 {
251         for (Task* t = taskList.first(); t != 0; t = taskList.next())
252                 t->finishScenario(sc);
253         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
254                 r->finishScenario(sc);
255 }
256
257 bool
258 Project::schedule(const QString& scenario)
259 {
260         bool error = FALSE;
261
262         TaskList sortedTasks(taskList);
263         sortedTasks.setSorting(CoreAttributesList::PrioDown, 0);
264         sortedTasks.setSorting(CoreAttributesList::SequenceUp, 1);
265         sortedTasks.sort();
266
267         bool done;
268         do
269         {
270                 done = TRUE;
271                 time_t slot = 0;
272                 for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
273                 {
274                         if (slot == 0)
275                         {
276                                 slot = t->nextSlot(scheduleGranularity);
277                                 if (slot == 0)
278                                         continue;
279                                 if (DEBUGPS(5))
280                                         qWarning("Task %s requests slot %s", t->getId().latin1(),
281                                                          time2ISO(slot).latin1());
282                                 if (slot < start || slot > end)
283                                 {
284                                         t->setRunaway();
285                                         if (DEBUGPS(5))
286                                                 qDebug("Marking task %s as runaway",
287                                                            t->getId().latin1());
288                                         error = TRUE;
289                                 }
290                         }
291                         t->schedule(slot, scheduleGranularity);
292                         done = FALSE;
293                 }
294         } while (!done);
295         
296         if (error)
297                 for (Task* t = sortedTasks.first(); t; t = sortedTasks.next())
298                         if (t->isRunaway())
299                                 if (t->getScheduling() == Task::ASAP)
300                                         t->fatalError(QString(
301                                                 "End of task %1 does not fit into the project time "
302                                                 "frame.").arg(t->getId()));
303                                 else
304                                         t->fatalError(QString(
305                                                 "Start of task %1 does not fit into the project time "
306                                                 "frame.").arg(t->getId()));
307
308         if (!checkSchedule(scenario))
309                 error = TRUE;
310
311         return !error;
312 }
313
314 bool
315 Project::checkSchedule(const QString& scenario)
316 {
317         int errors = 0;
318         for (Task* t = taskList.first(); t != 0; t = taskList.next())
319         {
320                 /* Only check top-level tasks, since they recursively check their sub
321                  * tasks. */
322                 if (t->getParent() == 0)
323                         t->scheduleOk(errors, scenario);
324                 if (errors >= 10)
325                 {
326                         qWarning(QString("Too many errors in %1 scenario. Giving up.")
327                                          .arg(scenario.lower()));
328                         break;
329                 }
330         }
331
332         return errors == 0;
333 }
334
335 void
336 Project::generateReports()
337 {
338         if (DEBUGPS(1))
339                 qWarning("Generating reports...");
340
341         // Generate task reports
342         for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
343                  h = htmlTaskReports.next())
344                 h->generate();
345
346         // Generate resource reports
347         for (HTMLResourceReport* r = htmlResourceReports.first(); r != 0;
348                  r = htmlResourceReports.next())
349                 r->generate();
350
351         // Generate account reports
352         for (HTMLAccountReport* r = htmlAccountReports.first(); r != 0;
353                  r = htmlAccountReports.next())
354                 r->generate();
355
356         // Generate calendar reports
357         for (HTMLWeeklyCalendar* r = htmlWeeklyCalendars.first(); r != 0;
358                  r = htmlWeeklyCalendars.next())
359                 r->generate();
360
361         // Generate export files
362         for (ExportReport* e = exportReports.first(); e != 0;
363                  e = exportReports.next())
364                 e->generate();
365
366         if( xmlreport )
367            xmlreport->generate();
368 #ifdef HAVE_ICAL
369 #ifdef HAVE_KDE
370         if( icalReport )
371            icalReport->generate();
372 #endif
373 #endif
374
375 }
376
377 bool
378 Project::needsActualDataForReports()
379 {
380         bool needsActual = FALSE;
381
382         // Generate task reports
383         for (HTMLTaskReport* h = htmlTaskReports.first(); h != 0;
384                  h = htmlTaskReports.next())
385                 if (h->getShowActual())
386                         needsActual = TRUE;
387         // Generate resource reports
388         for (HTMLResourceReport* h = htmlResourceReports.first(); h != 0;
389                  h = htmlResourceReports.next())
390                 if (h->getShowActual())
391                         needsActual = TRUE;
392
393         // Generate account reports
394         for (HTMLAccountReport* h = htmlAccountReports.first(); h != 0;
395                  h = htmlAccountReports.next())
396                 if (h->getShowActual())
397                         needsActual = TRUE;
398
399         return needsActual;
400 }
401
402 void
403 Project::setKotrus(Kotrus* k)
404 {
405         if (kotrus)
406                 delete kotrus;
407         kotrus = k;
408 }
409
410 bool
411 Project::readKotrus()
412 {
413         if (!kotrus)
414                 return TRUE;
415                 
416         for (Resource* r = resourceList.first(); r != 0; r = resourceList.next())
417                 r->dbLoadBookings(r->getKotrusId(), 0);
418
419         return TRUE;
420 }
421
422 bool
423 Project::updateKotrus()
424 {
425         return TRUE;
426 }
427
428 bool
429 Project::loadFromXML( const QString& inpFile )
430 {
431    QDomDocument doc;
432    QFile file( inpFile );
433
434    doc.setContent( &file );
435    qDebug(  "Loading XML " + inpFile );
436
437    QDomElement elemProject = doc.documentElement();
438
439    if( !elemProject.isNull())
440    {
441       parseDomElem( elemProject );
442    }
443    else
444    {
445       qDebug("Empty !" );
446    }
447    pass2(FALSE);
448    return true;
449 }
450
451
452 void Project::parseDomElem( QDomElement& parentElem )
453 {
454    QDomElement elem = parentElem.firstChild().toElement();
455
456    for( ; !elem.isNull(); elem = elem.nextSibling().toElement() )
457    {
458       QString tagName = elem.tagName();
459       
460       qDebug(  "|| elemType: " + tagName );
461       
462       if( tagName == "Task" )
463       {
464          QString tId = elem.attribute("Id");
465          Task *t = new Task( this, tId, QString(), 0, QString(), 0 );
466
467          t->loadFromXML( elem, this  );
468          addTask( t );
469       }
470       else if( tagName == "Name" )
471       {
472          setName( elem.text() );
473       }
474       else if( tagName == "Project" )
475       {
476          QString prjId = elem.attribute("Id");
477          addId( prjId );  // FIXME ! There can be more than one project ids!
478
479          prjId = elem.attribute("WeekStart");
480          setWeekStartsMonday( prjId == "Mon" );
481       }
482       else if( tagName == "Version" )
483          setVersion( elem.text() );
484       else if( tagName == "Priority" )
485          setPriority( elem.text().toInt());
486       else if( tagName == "start" )
487          setStart( elem.text().toLong());
488       else if( tagName == "end" )
489          setEnd( elem.text().toLong());
490                
491       // parseDomElem( elem );
492    }
493 }