{
public:
Account(const QString& i, const QString& n)
- : id(i), name(n), openingBalance(0.0) { }
+ : id(i), name(n), openingBalance(0.0)
+ {
+ kotrusId = "";
+ }
~Account() { };
const QString& getId() const { return id; }
#define COL_DEFAULT "#fffadd"
#define COL_WEEKEND "#ffec80"
#define COL_BOOKED "#ffc0a3"
-#define COL_HEADER "a5c2ff"
+#define COL_HEADER "#a5c2ff"
+#define COL_MILESTONE "#ff2a2a"
Project::Project()
{
sortedTasks.setSorting(TaskList::PrioDown);
sortedTasks.sort();
- for (int day = start; day < end; day += 60 * 60 * 24)
+ const time_t scheduleGranularity = ONEHOUR;
+ for (int day = start; day < end; day += scheduleGranularity)
{
bool done;
do
{
done = TRUE;
for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
- if (!t->schedule(day))
+ if (!t->schedule(day, scheduleGranularity))
done = FALSE;
} while (!done);
}
{
double load = t->getLoadOnDay(day);
QString bgCol = COL_DEFAULT;
- if (isWeekend(day))
+ if (t->isMilestone() && t->isActiveToday(day))
+ bgCol = COL_MILESTONE;
+ else if (isWeekend(day))
bgCol = COL_WEEKEND;
- else if (load > 0.0)
+ else if (t->isActiveToday(day))
bgCol = COL_BOOKED;
if (load > 0.0)
fprintf(f,
fprintf(f, "</tr>\n");
for (Task* t = taskList.first(); t != 0; t = taskList.next())
- if (r->isBusyWith(t))
+ if (r->isAssignedTo(t))
{
fprintf(f, "<tr>");
fprintf(f, "<td nowrap> <font size=\"-1\">%s</font></td>",
void addVacation(const QString& n, time_t s, time_t e)
{
vacationList.add(n, s, e);
- };
- bool isVacationDay(time_t d) { return vacationList.isVacationDay(d); }
+ }
+ bool isVacation(time_t d) { return vacationList.isVacation(d); }
void addResource(Resource* r)
{
{
return allowedFlags.contains(flag) > 0;
}
+
+ double convertToDailyLoad(long secs)
+ {
+ return ((double) secs / (8 * ONEHOUR));
+ }
private:
bool checkSchedule();
return FALSE;
}
- Resource* r = new Resource(id, name, proj->getMinEffort(),
+ Resource* r = new Resource(proj, id, name, proj->getMinEffort(),
proj->getMaxEffort(), proj->getRate());
TokenType tt;
QString token;
d = 1;
}
- struct tm t = { 0, 0, 0, d, m - 1, y - 1900, 0, 0, 0 };
+ struct tm t = { 0, 0, 0, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
return mktime(&t);
}
#include "Task.h"
#include "Project.h"
-double
-Resource::isAvailable(time_t date)
+Resource::Resource(Project* p, const QString& i, const QString& n,
+ double mie, double mae, double r) :
+ project(p), id(i), name(n), minEffort(mie), maxEffort(mae), rate(r)
{
- double bookedEffort = 0.0;
- for (Booking* b = jobs.first(); b != 0; b = jobs.next())
- if (date == b->getDate())
- bookedEffort += b->getEffort();
- return (maxEffort - bookedEffort);
+ // Monday
+ workingHours[1].append(new Interval(8 * ONEHOUR, 12 * ONEHOUR));
+ workingHours[1].append(new Interval(13 * ONEHOUR, 17 * ONEHOUR));
+ // Tuesday
+ workingHours[2].append(new Interval(8 * ONEHOUR, 12 * ONEHOUR));
+ workingHours[2].append(new Interval(13 * ONEHOUR, 17 * ONEHOUR));
+ // Wednesday
+ workingHours[3].append(new Interval(8 * ONEHOUR, 12 * ONEHOUR));
+ workingHours[3].append(new Interval(13 * ONEHOUR, 17 * ONEHOUR));
+ // Thursday
+ workingHours[4].append(new Interval(8 * ONEHOUR, 12 * ONEHOUR));
+ workingHours[4].append(new Interval(13 * ONEHOUR, 17 * ONEHOUR));
+ // Friday
+ workingHours[5].append(new Interval(8 * ONEHOUR, 12 * ONEHOUR));
+ workingHours[5].append(new Interval(13 * ONEHOUR, 17 * ONEHOUR));
}
bool
-Resource::book(Booking* b)
+Resource::isAvailable(time_t date, time_t duration, Interval& interval)
{
- jobs.append(b);
- return TRUE;
+ // Make sure that we don't overload the resource.
+ time_t bookedTime = duration;
+ Interval day = Interval(midnight(date), midnight(date) + ONEDAY - 1);
+ for (Booking* b = jobs.first(); b != 0; b = jobs.next())
+ if (day.contains(b->getInterval()))
+ bookedTime += b->getDuration();
+ if (project->convertToDailyLoad(bookedTime) > maxEffort)
+ return FALSE;
+
+ // Iterate through all the work time intervals for the week day.
+ const int dow = dayOfWeek(date);
+ for (Interval* i = workingHours[dow].first(); i != 0;
+ i = workingHours[dow].next())
+ {
+ interval = Interval(midnight(date), midnight(date));
+ interval.add(*i);
+ /* If there is an overlap between working time and the requested
+ * interval we exclude the time starting with the first busy
+ * interval in that working time. */
+ if (interval.overlap(Interval(date, date + duration - 1)))
+ {
+ for (Booking* b = jobs.first(); b != 0; b = jobs.next())
+ if (!interval.exclude(b->getInterval()))
+ return FALSE;
+ return TRUE;
+ }
+ }
+ return FALSE;
}
-double
-Resource::getLoadOnDay(time_t date)
+bool
+Resource::book(Booking* nb)
{
- double load = 0.0;
-
+ // Try first to append the booking
for (Booking* b = jobs.first(); b != 0; b = jobs.next())
- if (date == b->getDate())
- load += b->getEffort();
- return load;
+ if (b->getTask() == nb->getTask() &&
+ b->getProjectId() == nb->getProjectId() &&
+ b->getInterval().append(nb->getInterval()))
+ {
+ // booking appended
+ delete nb;
+ return TRUE;
+ }
+ jobs.append(nb);
+ return TRUE;
}
double
Resource::getLoadOnDay(time_t date, Task* task)
{
- double load = 0.0;
+ time_t bookedTime = 0;
+ const Interval day(midnight(date), midnight(date) + ONEDAY - 1);
for (Booking* b = jobs.first(); b != 0; b = jobs.next())
- if (date == b->getDate() && task == b->getTask())
- load += b->getEffort();
- return load;
+ {
+ if (day.contains(b->getInterval()) &&
+ (task == 0 || task == b->getTask()))
+ bookedTime += b->getDuration();
+ }
+ return project->convertToDailyLoad(bookedTime);
}
bool
-Resource::isBusyWith(Task* task)
+Resource::isAssignedTo(Task* task)
{
for (Booking* b = jobs.first(); b != 0; b = jobs.next())
if (task == b->getTask())
Resource*
ResourceList::getResource(const QString& id)
{
- Resource key(id, "");
+ Resource key(0, id, "");
return at(find(&key));
}
void
Resource::printText()
{
- printf("ID: %s\n", id.latin1());
- printf("Name: %s\n", name.latin1());
- printf("MinEffort: %3.2f MaxEffort: %3.2f Rate: %7.2f\n",
- minEffort, maxEffort, rate);
- for (Booking* j = jobs.first(); j != 0; j = jobs.next())
- printf("%s %5.2f %s\n", time2ISO(j->getDate()).latin1(),
- j->getEffort(),
- j->getTask()->getId().latin1());
}
void
#include <qlist.h>
#include <qstring.h>
+#include "Interval.h"
#include "VacationList.h"
class Project;
class Booking
{
public:
- Booking(time_t d, Task* t, double e) : date(d), task(t), effort(e) { }
+ Booking(const Interval& iv, Task* t, QString a = "",
+ QString i = "")
+ : interval(iv), task(t), account(a), projectId(i) { }
~Booking() { }
- time_t getDate() const { return date; }
+ time_t getStart() const { return interval.getStart(); }
+ time_t getEnd() const { return interval.getEnd(); }
+ time_t getDuration() const { return interval.getDuration(); }
+ Interval& getInterval() { return interval; }
+
Task* getTask() const { return task; }
- double getEffort() const { return effort; }
void setAccount(const QString a) { account = a; }
const QString& getAccount() const { return account; }
const QString& getProjectId() const { return projectId; }
private:
- // The day of the booking
- time_t date;
+ // The booked time period.
+ Interval interval;
// A pointer to the task that caused the booking
Task* task;
- // The effort (in man days) the resource is used that day.
- double effort;
// String identifying the KoTrus account the effort is credited to.
QString account;
// The Project ID
class Resource
{
public:
- Resource(const QString& i, const QString& n, double mie = 0.0,
- double mae = 1.0, double r = 0.0) :
- id(i), name(n), minEffort(mie), maxEffort(mae), rate(r) { }
+ Resource(Project* p, const QString& i, const QString& n, double mie = 0.0,
+ double mae = 1.0, double r = 0.0);
virtual ~Resource() { }
const QString& getId() const { return id; }
void setRate(double r) { rate = r; }
double getRate() const { return rate; }
- double isAvailable(time_t date);
+ bool isAvailable(time_t day, time_t duration, Interval& i);
bool book(Booking* b);
- double getLoadOnDay(time_t date);
- double getLoadOnDay(time_t date, Task* task);
+ double getLoadOnDay(time_t date, Task* task = 0);
- bool isBusyWith(Task* t);
+ bool isAssignedTo(Task* t);
void setKotrusId(const QString k) { kotrusId = k; }
const QString& getKotrusId() const { return kotrusId; }
void printText();
private:
+ Project* project;
+
// The ID of the resource. Must be unique in the project.
QString id;
// The resource name. E. g. real name or room number.
// KoTrus ID, ID by which the resource is known to KoTrus.
QString kotrusId;
+ QList<Interval> workingHours[7];
// List of all intervals the resource is not available.
VacationList vacationList;
// A list of all uses of the resource.
}
bool
-Task::schedule(time_t day)
+Task::schedule(time_t date, time_t duration)
{
// Task is already scheduled.
if (start != 0 && end != 0)
{
start = project->getStart();
}
- else
+ else if (earliestStart() > 0)
{
start = earliestStart();
+ done = 0.0;
+ costs = 0.0;
+ workStarted = FALSE;
+ tentativeEnd = date;
}
- done = 0.0;
- costs = 0.0;
- workStarted = FALSE;
- if (start == 0)
+ else
return TRUE; // Task cannot be scheduled yet.
}
}
else if (effort > 0)
{
+ /* Do not schedule anything before the start date lies within
+ * the interval. */
+ if (date + duration <= start || project->isVacation(date))
+ return TRUE;
/* The effort of the task has been specified. We have to look
* how much the resources can contribute over the following
* workings days until we have reached the specified
fatalError("No allocations specified for effort based task");
return TRUE;
}
- double dailyCosts = 0.0;
- if (isWorkingDay(day))
- {
- if (!bookResources(day, dailyCosts))
+ if (!bookResources(date, duration))
// fprintf(stderr,
// "No resource available for task '%s' on %s\n",
// id.latin1(),
-// time2ISO(day).latin1())
+// time2ISO(date).latin1())
;
- }
- /* If an account has been specified load account with the
- * accumulated costs of this day. */
- if (account)
- account->book(new Transaction(day, -costs, this));
if (done >= effort)
{
- end = day;
+ end = tentativeEnd;
return FALSE;
}
}
else
{
+ printf("%s %s\n", id.latin1(), time2ISO(start).latin1());
// Task is a milestone.
end = start;
return FALSE;
// Check that this is really a container task
if ((t = subTasks.first()) && (t != 0))
{
- if (t->getStart() > 0)
- nstart = t->getStart();
- if (t->getEnd() > 0)
- nend = t->getEnd();
+ /* Make sure that all sub tasks have been scheduled. If not we
+ * can't yet schedule this task. */
+ if (t->getStart() == 0 || t->getEnd() == 0)
+ return TRUE;
+ nstart = t->getStart();
+ nend = t->getEnd();
}
else
return TRUE;
for (t = subTasks.next() ; t != 0; t = subTasks.next())
{
- if (t->getStart() < start)
+ /* Make sure that all sub tasks have been scheduled. If not we
+ * can't yet schedule this task. */
+ if (t->getStart() == 0 || t->getEnd() == 0)
+ return TRUE;
+
+ if (t->getStart() < nstart)
nstart = t->getStart();
- if (t->getEnd() > end)
+ if (t->getEnd() > nend)
nend = t->getEnd();
}
- /* Make sure that all sub tasks have been scheduled. If not we can't
- * yet schedule this task. */
- if (nstart > 0 && nend > 0)
- {
- start = nstart;
- end = nend;
- return FALSE;
- }
-
- return TRUE;
+ start = nstart;
+ end = nend;
+ return FALSE;
}
bool
-Task::bookResources(time_t day, double& dailyCosts)
+Task::bookResources(time_t date, time_t duration)
{
bool allocFound = FALSE;
for (Allocation* a = allocations.first(); a != 0;
a = allocations.next())
{
- /* Move the start date to make sure that there is
- * some work going on on the start date. */
- if (!workStarted)
- start = day;
- double remaining;
- if ((remaining = a->getResource()->isAvailable(day)) > 0.0)
- {
- if (remaining > (a->getLoad() / 100.0))
- remaining = a->getLoad() / 100.0;
- if (remaining > (effort - done))
- remaining = effort - done;
- if (a->getResource()->book(new Booking(
- day, this, remaining)))
- {
- addBookedResource(a->getResource());
- done += remaining;
- dailyCosts += a->getResource()->getRate() * remaining;
- }
+ if (bookResource(a->getResource(), date, duration))
allocFound = TRUE;
- }
else
{
// fprintf(stderr,
// "Resource %s cannot be used for task '%s' on %s.\n",
// a->getResource()->getId().latin1(),
-// id.latin1(), time2ISO(day).latin1());
+// id.latin1(), time2ISO(date).latin1());
for (Resource* r = a->first(); r != 0; r = a->next())
- if ((remaining = r->isAvailable(day)) > 0.0)
+ if (bookResource(r, date, duration))
{
- if (remaining > (a->getLoad() / 100.0))
- remaining = a->getLoad() / 100.0;
- if (remaining > (effort - done))
- remaining = effort - done;
- if (r->book(new Booking(
- day, this, remaining)))
- {
- addBookedResource(a->getResource());
- done += remaining;
- dailyCosts += r->getRate() * remaining;
- }
allocFound = TRUE;
break;
}
}
}
- if (allocFound)
- workStarted = TRUE;
return allocFound;
}
bool
+Task::bookResource(Resource* r, time_t date, time_t duration)
+{
+ Interval interval;
+
+ if (r->isAvailable(date, duration, interval))
+ {
+ double intervalLoad = project->convertToDailyLoad(
+ interval.getDuration());
+ r->book(new Booking(interval, this,
+ account ? account->getKotrusId() : QString(""),
+ project->getId()));
+ addBookedResource(r);
+
+ /* Move the start date to make sure that there is
+ * some work going on on the start date. */
+ if (!workStarted)
+ {
+ start = date;
+ workStarted = TRUE;
+ }
+
+ tentativeEnd = interval.getEnd();
+ done += intervalLoad;
+ //costs += r->getRate() * intervalLoad;
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool
Task::isScheduled()
{
return ((start != 0 && end != 0) || !subTasks.isEmpty());
time_t
Task::earliestStart()
{
- time_t day = 0;
+ time_t date = 0;
for (Task* t = previous.first(); t != 0; t = previous.next())
- if (t->getEnd() > day)
- day = t->getEnd();
+ if (t->getEnd() > date)
+ date = t->getEnd() + 1;
/* If the task duration is enforced by length (and not effort) a task
* starts the next working day after the previous tasks have been
* finished. With effort based scheduling we can schedule multiple
- * tasks per day. */
- if (day > 0 && length > 0)
- day = nextWorkingDay(day);
+ * tasks per date. */
+ if (date > 0 && length > 0)
+ date = nextWorkingDay(date);
- return day;
+ return date;
}
double
-Task::getLoadOnDay(time_t day)
+Task::getLoadOnDay(time_t date)
{
double load = 0.0;
for (Resource* r = bookedResources.first(); r != 0;
r = bookedResources.next())
{
- load += r->getLoadOnDay(day, this);
+ load += r->getLoadOnDay(date, this);
}
return load;
}
if (tms->tm_wday < 1 || tms->tm_wday > 5)
return FALSE;
- return !project->isVacationDay(d);
+ return !project->isVacation(d);
}
time_t
bool xRef(QDict<Task>& hash);
QString resolveId(QString relId);
- bool schedule(time_t reqStart);
+ bool schedule(time_t reqStart, time_t duration);
bool isScheduled();
bool scheduleOK();
+ bool isMilestone() const { return start != 0 && start == end; }
+ bool isActiveToday(time_t date) const
+ {
+ Interval day(midnight(date), midnight(date) + ONEDAY - 1);
+ Interval work(start, end);
+ return day.overlap(work);
+ }
+
void setAccount(Account* a) { account = a; }
void addFlag(QString flag)
private:
bool scheduleContainer();
- bool bookResources(time_t day, double& costs);
+ bool bookResource(Resource* r, time_t day, time_t duration);
+ bool bookResources(time_t day, time_t duration);
bool isWorkingDay(time_t day) const;
time_t nextWorkingDay(time_t day) const;
time_t earliestStart();
// Percentage of completion of the task
int complete;
- // The following 3 variables are used during scheduling.
+ // The following 4 variables are used during scheduling.
double costs;
double done;
bool workStarted;
+ time_t tentativeEnd;
// Account where the costs of the task are credited to.
Account* account;
return tms->tm_mday;
}
+int
+dayOfWeek(time_t t)
+{
+ struct tm* tms = localtime(&t);
+ return tms->tm_wday;
+}
+
+time_t
+midnight(time_t t)
+{
+ struct tm* tms = localtime(&t);
+ tms->tm_sec = tms->tm_min = tms->tm_hour = 0;
+ return mktime(tms);
+}
+
QString time2ISO(time_t t)
{
struct tm* tms = localtime(&t);
static QString s;
- s.sprintf("%04d-%02d-%02d", 1900 + tms->tm_year, 1 + tms->tm_mon,
- tms->tm_mday);
+ s.sprintf("%04d-%02d-%02d %02d:%02d", 1900 + tms->tm_year, 1 + tms->tm_mon,
+ tms->tm_mday, tms->tm_hour, tms->tm_min);
return s;
}
#include <qstring.h>
#define MAXTIME 0x7FFFFFFF
+#define ONEDAY (60 * 60 * 24)
+#define ONEHOUR (60 * 60)
const char* monthAndYear(time_t d);
int dayOfMonth(time_t d);
+int dayOfWeek(time_t d);
+
+time_t midnight(time_t t);
+
QString time2ISO(time_t t);
#endif
Interval* i1 = static_cast<Interval*>(it1);
Interval* i2 = static_cast<Interval*>(it2);
- if (i1->start == i2->start)
+ if (i1->getStart() == i2->getStart())
{
- if (i1->end == i2->end)
+ if (i1->getEnd() == i2->getEnd())
return 0;
else
- return i1->end - i2->end;
+ return i1->getEnd() - i2->getEnd();
}
else
- return i1->start - i2->start;
+ return i1->getStart() - i2->getStart();
}
bool
-VacationList::isVacationDay(time_t day)
+VacationList::isVacation(time_t date)
{
- Interval* i;
- for (i = first(); i != 0 && day <= i->getEnd(); i = next())
- if (i->getStart() <= day && day <= i->getEnd())
+ VacationInterval* i;
+ for (i = first(); i != 0 && date <= i->getEnd(); i = next())
+ if (i->contains(date))
return TRUE;
return FALSE;
#include <qlist.h>
#include <qstring.h>
-class Interval
+#include "Interval.h"
+
+class VacationInterval : public Interval
{
public:
- Interval() { start = 0; end = 0; }
- Interval(const QString& n, time_t s, time_t e)
- : name(n), start(s), end(e) { }
- virtual ~Interval() { }
+ VacationInterval() { }
+
+ VacationInterval(const QString& n, time_t s, time_t e)
+ : Interval(s, e), name(n) { }
+ virtual ~VacationInterval() { }
const QString& getName() const { return name; }
- time_t getStart() const { return start; }
- time_t getEnd() const { return end; }
+private:
QString name;
- time_t start;
- time_t end;
} ;
-class VacationList : protected QList<Interval>
+class VacationList : protected QList<VacationInterval>
{
public:
VacationList() { setAutoDelete(TRUE); }
void add(const QString& name, time_t start, time_t end)
{
- inSort(new Interval(name, start, end));
+ inSort(new VacationInterval(name, start, end));
}
- bool isVacationDay(time_t day);
+ bool isVacation(time_t date);
protected:
virtual int compareItems(QCollection::Item i1, QCollection::Item i2);
minute and second are optional. If not specified the values are set to
0.
+TIME: A time in the format HH:MM.
+
UNIT: May be
h for hours,
d for days,
m for months,
y for years.
+WEEKDAY: May be
+Mon for Monday
+Tue for Tuesday
+Wed for Wednesday
+Thu for Thursday
+Fri for Friday
+Sat for Saturday
+Sun for Sunday
+
INTEGER: An integer number.
REAL: A real number (e.g. 3.14).
the vacation.
-------------------------------------------------------------------------------
+hours <weekday> <from>-<to>[,<from>-<to>]
+weekday: WEEKDAY
+from: TIME
+to: TIME
+
+Sets the working hours to the specified intervals. If no hours are
+specified for a day, the global definitions are used.
+
+-------------------------------------------------------------------------------
===============================================================================
vacation name <start> [- <end>]
vacation, not two days.
===============================================================================
-priority <value>
+hours <weekday> <from>-<to>[,<from>-<to>]
+weekday: WEEKDAY
+from: TIME
+to: TIME
+
+Sets the working hours to the specified intervals. The values are used
+as default values for all resources defined afterwards. The default
+values are 08:00-12:00 and 13:00-17:00 from Mon - Fri.
+
+===============================================================================
+workingHoursPerDay <value>
value: INTEGER
+Specifies the number of working hours per day. This value is used to
+calculate the daily load from the number of booked hours. If
+workingHoursPerDay is set to 8 and a resource is used for 12 hours on
+a day, the daily load is 1.5.
+
+===============================================================================
+priority <value> value: INTEGER
+
The default scheduling priority for tasks. The value must be between 1
and 1000 and is inherited by all tasks if no other priority is
specified.
used as filters during report generation.
===============================================================================
+timingResolution <value> <unit>
+value: INTERGER
+unit: UNIT
+
+Sets the minimum timing resolution. The smaller the value, the longer the schedulings process needs. The default is 1 hour.
+
+===============================================================================
htmlTaskReport <filename>
filename: STRING
int main(int argc, char *argv[])
{
-
QApplication a(argc, argv, false);
Project p;
project sl80 "SuSE Linux 8.0" "$Id$" 2001-09-28 2002-04-01
htmlTaskReport "test.html" {
- columns no, name, start, end, daily
+ columns no, name, start, end, minStart, daily
start 2001-10-01
end 2002-04-01
}
account acc1 "Work" { kotrusId "SuSE Linux" balance 1000.0 }
-resource ro "Ruediger Oertel" # Dies ist ein Kommentar
+resource ro "Ruediger Oertel"
resource kukuk "Thorsten Kukuk"
resource cs "Chris Schlaeger"
resource mem "Muhamed Memovic"