dotProject Dependency Calculations

For the past month or so, I have been fighting with an annoying little dotProject issue. It seemed like a relatively simple thing, but it just kept cropping up. About 3am local time on Sunday morning a few weeks ago, I finally tracked it down and fixed the first half of it… the second half has since been fixed.

Here's the scenario:

You have a series of tasks (A-B-C-D-E) which are sequentially dependent on each other and may or may not be adjacent in terms of start and end dates. Now, as you shift any of the dates forward, each task after it should shift if and only if the tasks it depends on push the task foward. The most common way to do this within dotProject is to mark each task with “dependency tracking on” and then set the Start Date for each task based on its dependencies.

The bug in dotProject was annoying but very subtle and easy to miss if you had complex dependency trees. If you updated task A, it would update task B correctly. It would update the Start Date of task C but nothing farther than that. Therefore, your Gantt Chart would start looking screwy as task C ended before it started, hell would freeze over, the Cubs would win the series, and Project Managers could choose all three.

So here's the troublesome code in v2.0.4*. In /dotproject/modules/tasks/do_task_aed.php around line 169, you should see this:

// shifted new start date
$nsd = new CDate ($tempTask->get_deps_max_end_date( $tempTask ) );

// calc shifting span old start ~ new start
$d = $tsd->calcDurationDiffToDate($nsd);

// update start date based on dep
$obj->update_dep_dates( $obj->task_id, $d );

// appropriately shifted end date
$ned = $ted->addDuration($d, $obj->task_duration_type);

$obj->task_end_date = $ned->format( FMT_DATETIME_MYSQL );

This would figure out the latest end date of the dependencies and shift the start date accordingly. It would determine the difference between the old and new start date and then shift the end date by the same difference. Seems logical and should work just fine… but it doesn't. It seems that a mistmatch in units causes all the trouble, so here's my current solution:

// shifted new start date
$nsd = new CDate ($tempTask->task_start_date);

// appropriately shifted end date
$ned = $nsd->addDuration($obj->task_duration, $obj->task_duration_type);

$obj->task_end_date = $ned->format( FMT_DATETIME_MYSQL );

So what this does is takes the new start date and then calculates a new end date based on the duration. It's simple and moves us closer to a solution, but it's not perfect. I've already found a handful of Use Cases where this breaks down such as maintenance tasks where only a few hours are allocated for a particular period. In those scenarios, instead of simply adding the duration to the start date, you'd want to shift the end date accordingly.

* This fix has been applied to both branches.