OSDN Git Service

Automatically align underspecified tasks boundaries on project boundaries when possible.
authorGrégoire Barbier <g@g76r.eu>
Sat, 1 Aug 2009 11:52:17 +0000 (13:52 +0200)
committerChris Schlaeger <cs@kde.org>
Fri, 21 Aug 2009 18:00:10 +0000 (20:00 +0200)
Somme common underspecification cases can be automatically completed,
before scheduling, by aligning the task boundaries on project
boundaries. This is now done.
This enables syntax simplication, and therefore ease of use for new
users, for the following two cases (the third one was actually
already supported).

(i) Project-wide tasks

Some tasks, such as management, last from project start to project
end. They no longer have to specify "start" and "end" with values
that change each time the project boundaries change.
For instance, this syntax is now accepted for a project-wide task:
task mnm "Management & Meetings" {
  # no start date since it can be guessed as aligned on project start
  # no end date since it can be guessed as aligned on project end
  priority 1000 # no other task should steal time on this one
  allocate john {
    limits { weeklymax 0.5d dailymax 1h } # 4h/w but for short weeks
    alternative joe select maxloaded # Joe when John is on vacation
  }
}
This only works for tasks that have no incompatible soft constraints.
That is, no previous tasks if the start date is to be aligned on
project start date, and no followers if the end date is to be aligned
on project end date.

(ii) Half-project-wide tasks

Some tasks, such as support, last from a start point to the project
end (in ASAP scheduling) or from the project start to an end point
(in ALAP scheduling). They no longer have to specify the boundary
that is to be aligned to a project boundary.
For instance, this syntax is now accepted for a task which lasts
until the project end:
task support "Support" {
  # no end date since it can be guessed as aligned on project end
  depends !deployment
  priority 1000 # no other task should steal time on this one
  allocate team { limits { weeklymax 0.5d dailymax 1h } }
}
The same restrition about soft constraints applies as in case (i).

(iii) Tasks that we do not yet know the details

Previously, several cases of unknown content tasks were silently
converted to milestones to avoid errors during early stages of
project specification. This is still the case for tasks that does
not match the two previous cases.
For instance, such a task will be automatically converted to
milestone:
task integration "Integration Phase" {
  depends !development
  # not yet decided, let it be a milestone until further details
}

Nota:
In some cases this patch changes the behaviour of TJ since some tasks
where previously converted to milestone and are now extended to the
project boundaries, this is the case of tasks that only contained
a start specification (therefore in ASAP scheduling) and had no
followers. The new behaviour seems more desirable than the former one
since such tasks should be explicitly marked as milestones if they
are so: implicit conversion to milestone is only there as a
convenience for early project specification, not as a regular syntax.

TestSuite/HTML-Reports/Complete.tjp
TestSuite/Syntax/Correct/BoundaryGuesser.tjp [new file with mode: 0644]
taskjuggler/Project.cpp
taskjuggler/Task.cpp

index 0018efa..045ce1a 100644 (file)
@@ -8,18 +8,22 @@ resource tux2 "Tux2"
 task m1 "Milestone Task" {
   task m2 "M2" {
     start 2005-10-19
+    milestone
   }
   task m3 "M3" {
     task m4 "M4" {
       start 2005-10-22
+      milestone
     }
   }
   task m5 "M5" {
     start 2005-12-01
+    milestone
   }
   task m6 "M6" {
     task m7 "M7" {
       start 2005-12-24
+      milestone
     }
   }
 }
diff --git a/TestSuite/Syntax/Correct/BoundaryGuesser.tjp b/TestSuite/Syntax/Correct/BoundaryGuesser.tjp
new file mode 100644 (file)
index 0000000..e15828c
--- /dev/null
@@ -0,0 +1,75 @@
+project prj "Project" "1.0" 2009-1-1 - 2009-6-30 {
+  timeformat "%Y-%m-%d"
+  now 2009-1-1
+  scenario plan "Plan" {
+    projection { strict }
+    scenario forcheck "Just for checking multi-scenario mode"
+  }
+}
+
+resource people "People" {
+  resource john "John"
+  resource team "Team of workers" {
+    resource joe "Joe"
+    resource jim "Jim"
+  }
+}
+
+task prj "My Project" {
+  task planning "Planning Phase" {
+    # no start date since it can be guessed to be the aligned on project start
+    effort 6d
+    allocate john
+  }
+  task mnm "Management & Meetings" {
+    # no start date since it can be guessed as aligned on project start
+    # no end date since it can be guessed as aligned on project end
+    priority 1000 # no other task should steal time on this one
+    allocate john {
+      limits { weeklymax 0.5d dailymax 1h } # 4h/w but for short weeks
+      alternative joe select maxloaded # Joe when John is on vacation
+    }
+  }
+  task support "Support" {
+    # no end date since it can be guessed as aligned on project end
+    depends !deployment
+    priority 1000 # no other task should steal time on this one
+    allocate team { limits { weeklymax 0.5d dailymax 1h } }
+  }
+  task design "Design Phase" {
+    depends !planning
+    allocate john
+    effort 15d
+  }
+  task development "Development Phase" {
+    depends !design
+    allocate team
+    effort 30d
+  }
+  task integration "Integration Phase" {
+    depends !development
+    # not yet decided, let it be a milestone until further details
+  }
+  task deployment "Deployment Phase" {
+    depends !integration
+    # not yet decided, let it be a milestone until further details
+  }
+  task userdoc "User Documentation" {
+    depends !design
+    allocate people
+    effort 10d
+  }
+  task admindoc "Administrator Documentation" {
+    depends !development
+    allocate people
+    effort 6d
+  }
+}
+
+taskreport "Gantt Chart" {
+  headline "Project Gantt Chart"
+  columns hierarchindex, name, start, end, effort, duration, chart
+  timeformat "%a %Y-%m-%d"
+  loadunit days
+  sorttasks tree,sequenceup
+}
index 2cb7dc3..00c0d03 100644 (file)
@@ -508,6 +508,15 @@ Project::pass2(bool noDepCheck)
     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
         (*tli)->xRef(idHash);
 
+    /* Now we can copy the missing values from the plan scenario to the other
+     * scenarios. */
+    if (scenarioList.count() > 1)
+    {
+        for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
+             *sli; ++sli)
+            overlayScenario(0, (*sli)->getSequenceNo() - 1);
+    }
+
     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
     {
         // Set dates according to implicit dependencies
@@ -524,15 +533,6 @@ Project::pass2(bool noDepCheck)
     for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
         (*rli)->saveSpecifiedBookings();
 
-    /* Now we can copy the missing values from the plan scenario to the other
-     * scenarios. */
-    if (scenarioList.count() > 1)
-    {
-        for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
-             *sli; ++sli)
-            overlayScenario(0, (*sli)->getSequenceNo() - 1);
-    }
-
     // Now check that all tasks have sufficient data to be scheduled.
     setProgressInfo(i18n("Checking scheduling data..."));
     bool error = false;
index dbcbc51..b7e8ecc 100644 (file)
@@ -1522,26 +1522,102 @@ Task::implicitXRef()
 
     if (!isMilestone() && isLeaf())
     {
-        /* Automatic milestone marker. As a convenience we convert tasks that
-         * only have a start or end criteria as a milestone. This is handy
-         * when in the early stage of a project draft, when you just want to
-         * specify the project outline and fill in subtasks and details
-         * later. */
-        bool hasStartSpec = false;
-        bool hasEndSpec = false;
-        bool hasDurationSpec = false;
+        /* Automatic boundary guesser for tasks that are underspecified in
+         * term of start/end/duration. As a convenience we add start and or
+         * end boundaries to task that are likely to be aligned on project
+         * start or end dates, and convert tasks to milestones in worst
+         * cases (i.e. when dependecies disables the tasks to be aligned
+         * on the project boundaries).
+         * This is handy when in the early stage of a project draft, when
+         * you just want to specify the project outline and fill in
+         * subtasks and details later.
+         * This is also handy even after the early stage for tasks that are
+         * actually aligned on one or both project boundary. */
+        int milestoneBallots = 0;
         for (int sc = 0; sc < project->getMaxScenarios(); ++sc)
         {
-            if (scenarios[sc].specifiedStart != 0 || !depends.isEmpty())
+            bool hasStartSpec = false;
+            bool hasEndSpec = false;
+            bool hasDurationSpec = false;
+            bool hasPreviousEvenInherited = false;
+            bool hasDependsEvenIhnerited = false;
+            bool hasFollowersEvenInherited = false;
+            bool hasPrecedesEvenInherited = false;
+            for (Task* task = this; task; task = task->getParent())
+            {
+                hasPreviousEvenInherited |= task->hasPrevious();
+                hasDependsEvenIhnerited |= !task->depends.isEmpty();
+                hasFollowersEvenInherited |= task->hasFollowers();
+                hasPrecedesEvenInherited |= !task ->precedes.isEmpty();
+            }
+            if (scenarios[sc].specifiedStart != 0 || hasDependsEvenIhnerited)
                 hasStartSpec = true;
-            if (scenarios[sc].specifiedEnd != 0 || !precedes.isEmpty())
+            if (scenarios[sc].specifiedEnd != 0 || hasPrecedesEvenInherited)
                 hasEndSpec = true;
             if (scenarios[sc].duration != 0 || scenarios[sc].length != 0 ||
                 scenarios[sc].effort != 0)
                 hasDurationSpec = true;
+            /*printf("O: %s/%d: (%g-%g %g) %d [%d %d] <-%d %d->\n", getId().ascii(),
+                    sc, scenarios[sc].duration, scenarios[sc].length,
+                    scenarios[sc].effort, (int)hasDurationSpec, (int)hasStartSpec,
+                    (int)hasEndSpec, (int)hasPreviousEvenInherited,
+                    (int)hasFollowersEvenInherited);*/
+            // ASAP tasks lacking start specification
+            // - --> * (no previous)
+            // - x-> * (no previous)
+            if (!hasPreviousEvenInherited && !hasStartSpec
+                && scheduling == ASAP)
+            {
+                hasStartSpec = true;
+                setSpecifiedStart(sc, project->getStart());
+                //printf("A: auto aligning %s on start date\n", getId().ascii());
+            }
+            // ALAP tasks lacking end specification
+            // * <-- - (no followers)
+            // * <-x - (no followers)
+            if (!hasFollowersEvenInherited && !hasEndSpec
+                && scheduling == ALAP)
+            {
+                hasEndSpec = true;
+                setSpecifiedEnd(sc, project->getEnd());
+                //printf("B: auto aligning %s on end date\n", getId().ascii());
+            }
+            // tasks with no duration spec and at most one boundary spec
+            if  (!hasDurationSpec && !(hasStartSpec && hasEndSpec))
+            {
+                // ASAP tasks with neither end nor duration spec
+                // * --> - (no followers)
+                if (!hasFollowersEvenInherited && !hasEndSpec
+                    && scheduling == ASAP)
+                {
+                    setSpecifiedEnd(sc, project->getEnd());
+                    //printf("C: auto aligning %s on end date\n", getId().ascii());
+                }
+                // ALAP tasks with neither start nor duration spec
+                // - <-- * (no previous)
+                else if (!hasPreviousEvenInherited && !hasStartSpec
+                    && scheduling == ALAP)
+                {
+                    setSpecifiedStart(sc, project->getStart());
+                    //printf("D: auto aligning %s on start date\n", getId().ascii());
+                }
+                // other cases, e.g.:
+                // D --> D
+                // D <-- D
+                // | --> D (with previous)
+                // D <-- | (with followers)
+                else
+                {
+                    ++milestoneBallots;
+                    //printf("E: voting for %s conversion to milestone\n", getId().ascii());
+                }
+            }
         }
-        if  (!hasDurationSpec && (hasStartSpec ^ hasEndSpec))
+        if (milestoneBallots == project->getMaxScenarios())
+        {
             milestone = true;
+            //printf("F: auto converting %s to milestone\n", getId().ascii());
+        }
     }
 }