home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Freeware / Programare / dotproject / modules / tasks / organizer.php < prev    next >
Encoding:
PHP Script  |  2003-12-12  |  16.1 KB  |  499 lines

  1. <?php /* TASKS $Id: organizer.php,v 1.17 2003/12/11 20:30:21 gregorerhardt Exp $ */
  2.  
  3. /*
  4.  * Dynamic Tasks Organizer - by J. Christopher Pereira
  5.  *
  6.  * Consider:
  7.  *    - order by priorities
  8.  *    - other related persons time availability
  9.  *
  10.  * Constraints:
  11.  *    - other tasks
  12.  *    - task dependencies
  13.  *
  14.  */
  15.  
  16. $errors = false;
  17. $tasks = array();
  18. $actions = false;
  19.  
  20. $do = isset( $_REQUEST['do'] ) ? $_REQUEST['do'] : 'conf';
  21. $set_duration = isset( $_REQUEST['set_duration'] ) ? $_REQUEST['set_duration'] : null;
  22. $set_dynamic = isset( $_REQUEST['set_dynamic'] ) ? $_REQUEST['set_dynamic'] : null;
  23.  
  24. $df = $AppUI->getPref('SHDATEFORMAT');
  25.  
  26. $NO_DATE = "0000-00-00 00:00:00";
  27.  
  28. function task_link($task) {
  29.     return "<a href='index.php?m=tasks&a=addedit&task_id=" . $task["task_id"] . "'>" . $task["task_name"] . "</a>";
  30. }
  31.  
  32. function search_task($task_id) {
  33.     global $tasks;
  34.     for($i = 0; $i < count($tasks) ; $i++) {
  35.         if($tasks[$i]["task_id"] == $task_id) return $i;
  36.     }
  37.     return -1;
  38. }
  39.  
  40. function log_info($msg) {
  41.     global $option_debug;
  42.     if($option_debug) {
  43.         echo "$msg<br />";
  44.     }
  45. }
  46.  
  47. function log_action($msg) {
  48.     global $action;
  49.     echo "  <font color=red size=2>$msg</font><br />";
  50.     $action = true;
  51. }
  52.  
  53. function log_error($msg, $fields = "") {
  54.     global $action;
  55.     echo "<font color=red size=1>ERROR: $msg</font><br />$fields<hr>";
  56.     $action = true;
  57. }
  58.  
  59. function log_warning($msg, $fields = "") {
  60.     global $show_warnings;
  61.     echo "WARNING: $msg<br />$fields<hr>";
  62. }
  63.  
  64. function fixate_task($task_index, $time, $dep_on_task) {
  65.  
  66.     // WARNING: task_index != task_id !!!
  67.  
  68.     global $tasks, $do, $option_advance_if_possible, $AppUI, $df;
  69.  
  70.     // don't fixate tasks before now
  71.  
  72.     if($time < time()) {
  73.         $time = time();
  74.     }
  75.     
  76.     $start_date = $time;
  77.     $end_date = $start_date;
  78.     $durn = convert2days( $tasks[$task_index]["task_duration"], $tasks[$task_index]["task_duration_type"] );
  79.     $end_date += $durn * SECONDS_PER_DAY;
  80.     
  81.     // Complex SQL explanation:
  82.     //
  83.     // Objective: Check tasks overlapping only when
  84.     // a user is vital for both tasks
  85.     //
  86.     // Definition of "vital for one task": when a task is assigned to user and total_users <= 2
  87.     // (for example: if task is assigned to tree o more users, he is not vital).
  88.     //
  89.     // Thus, a user is vital for both tasks <=>
  90.     //    - total_users <= 2 for both tasks
  91.     //    - and he apears in both tasks
  92.     //
  93.     // Thus, in both tasks (say 4 and 10), a there will be a vital user <=>
  94.     //    - "number of tasks with total_users <= 2"
  95.     //      = rows("select count(*) as num_users from user_tasks
  96.     //      where task_id=4 or task_id=10
  97.     //      group by task_id having num_users <= 2") == 2;
  98.     //
  99.     //    - and "number of users which appears in both tasks"
  100.     //      = rows("select count(*) as frec
  101.     //      from user_tasks where task_id=4 or task_id=10
  102.     //      group by user_id having frec = 2") > 0
  103.  
  104.     $t1_start = $start_date;
  105.     $t1_end = $end_date;    
  106.  
  107.     foreach($tasks as $task2) {
  108.         $t2_start = db_dateTime2unix( $task2["task_start_date"] );
  109.         $t2_end = db_dateTime2unix( $task2["task_end_date"] );        
  110.         
  111.         if($task2["fixed"] && (
  112.                                ($t1_start >= $t2_start && $t1_start <= $t2_end)
  113.                                || ($t1_end >= $t2_start && $t1_end <= $t2_end))
  114.            ) {
  115.             // tasks are overlapping
  116.  
  117.             if(!$option_advance_if_possible || $task2["task_percent_complete"] != 100) {
  118.  
  119.                 $t1 = $tasks[$task_index]["task_id"];
  120.                 $t2 = $task2["task_id"];
  121.                 
  122.                 if ( $option_check_vital_users ) {                    
  123.                     $sql1 = "select count(*) as num_users from user_tasks where task_id=$t1 or task_id=$t2 group by task_id having num_users <= 2";
  124.                     $sql2 = "select count(*) as frec from user_tasks where task_id=$t1 or task_id=$t2 group by user_id having frec = 2";
  125.                     $vital = mysql_num_rows(mysql_query($sql1)) == 2 && mysql_num_rows(mysql_query($sql2)) > 0;
  126.                 } else {
  127.                     $vital = true;
  128.                 }
  129.                 
  130.                 if($vital) {
  131.                     log_info("Task can't be set to [" . formatTime($start_date) . " - ". formatTime($end_date) . "] due to conflicts with task " . task_link($task2) . ".");
  132.                     // OBS: I'm asuming the dependent task will start next day
  133.                     fixate_task($task_index, $t2_end + SECONDS_PER_DAY, $dep_on_task);
  134.                     return;                    
  135.                 } else {
  136.                     log_info("Task conflicts with task " . task_link($task2) . " but there are no vital users.");
  137.                 }
  138.             } else {
  139.                 log_info("Task " . task_link($task2) . " is complete, I won't check if it is overllaping");
  140.             }
  141.         }
  142.     }
  143.     
  144.     $tasks[$task_index]["fixed"] = true;
  145.  
  146.     // be quite if nothing changes
  147.  
  148.     if (db_dateTime2unix( $tasks[$task_index]["task_start_date"] ) == $start_date &&
  149.         db_dateTime2unix( $tasks[$task_index]["task_end_date"] ) == $end_date ) {
  150.         log_info("Nothing changed, still programmed for [" . formatTime($start_date) . " - " . formatTime($end_date) . "]");
  151.         return;
  152.     }    
  153.     
  154.     $tasks[$task_index]["task_start_date"] = db_unix2dateTime( $start_date );
  155.     $tasks[$task_index]["task_end_date"] = db_unix2dateTime( $end_date );    
  156.  
  157.     if($do == "ask") {
  158.         if($dep_on_task) {
  159.             log_action("I will fixate task " . task_link($tasks[$task_index]) . " to " . formatTime($start_date) . " (depends on " .  task_link($dep_on_task) . ")");
  160.         } else {
  161.             log_action("I will fixate task " . task_link($tasks[$task_index]) . " to " . formatTime($start_date) . " (no dependencies)");
  162.         }
  163.         
  164.         // echo "<input type=hidden name=fixate_task[" . $tasks[$task_index]["task_id"] . "] value=y>";
  165.     } else if($do == "fixate") {
  166.         log_action("Task " . task_link($tasks[$task_index]) . " fixated to " . formatTime($start_date) );
  167.         $sql = "update tasks set task_start_date = '" . db_unix2dateTime($start_date) . "', task_end_date = '" .  db_unix2dateTime($end_date) . "' where task_id = " . $tasks[$task_index]["task_id"];
  168.         mysql_query($sql);
  169.     }
  170.     
  171. }
  172.  
  173. function get_last_children($task) {
  174.     // returns the last children (leafs) from $task
  175.     $arr = array();
  176.  
  177.     // query children from task
  178.     $sql = "select * from tasks where task_parent=" . $task["task_id"];
  179.     $query = mysql_query($sql);
  180.     if(mysql_num_rows($query)) {
  181.         // has children
  182.         while($row = mysql_fetch_array($query)) {
  183.             if($row["task_id"] != $task["task_id"]) {
  184.                 // add recursively children of children to $arr
  185.                 $sub = get_last_children($row);
  186.                 array_splice($arr, count($arr), 0, $sub);
  187.             }
  188.         }
  189.     } else {
  190.         // it's a leaf
  191.         array_push($arr, $task);
  192.     }
  193.     return $arr;
  194. }
  195.  
  196. function process_dependencies($i) {
  197.     global $tasks, $option_advance_if_possible;
  198.  
  199.     if($tasks[$i]["fixed"]) return;  
  200.  
  201.     log_info("<div style='padding-left: 1em'>Dependecies for '" . $tasks[$i]["task_name"] . "':<br />");
  202.  
  203.     // query dependencies for this task
  204.  
  205.     $query = mysql_query("select tasks.* from tasks,task_dependencies where task_id=dependencies_req_task_id and dependencies_task_id=" . $tasks[$i]["task_id"]);
  206.  
  207.     if(mysql_num_rows($query) != 0) {
  208.         $all_fixed = true;
  209.         $latest_end_date = null;       
  210.  
  211.         // store dependencies in an array (for adding more entries on the fly)
  212.  
  213.         $dependencies = array();
  214.         while($row = mysql_fetch_array($query)) {
  215.             array_push($dependencies, $row);
  216.         }        
  217.  
  218.         $d = 0;
  219.         
  220.         while($d < count($dependencies)) {
  221.  
  222.             $row = $dependencies[$d];
  223.             $index = search_task($row["task_id"]);
  224.  
  225.             if($index == -1) {
  226.                 // task is not listed => it's a task group
  227.                 // => $i depends on all its subtasks
  228.                 // => add all subtasks to the dependencies array
  229.  
  230.                 log_info("- task '" . $row["task_name"] . "' is a task group (processing subtask's dependencies)");
  231.  
  232.                 $children = get_last_children($row);
  233.                 // replace this taskgroup with all its subtasks
  234.  
  235.                 array_splice($dependencies, $d, 1, $children);
  236.  
  237.                 continue;
  238.             }
  239.  
  240.             log_info(" - '" . $tasks[$index]["task_name"] . ($tasks[$index]["fixed"]?" (FIXED)":"") . "'");
  241.  
  242.             // TODO: Detect dependencies loops (A->B, B->C, C->A)
  243.  
  244.             process_dependencies($index);
  245.  
  246.             if(!$tasks[$index]["fixed"]) {
  247.                 $all_fixed = false;
  248.             } else {
  249.                 // ignore dependencies of finished tasks if option is enabled
  250.                 if(!$option_advance_if_possible || $tasks[$index]["task_percent_complete"] != 100) {
  251.                     // get latest end_date
  252.                     $end_date = db_dateTime2unix( $tasks[$index]["task_end_date"] );
  253.  
  254.                     if(!$latest_end_date || $end_date > $latest_end_date) {
  255.                         $latest_end_date = $end_date;
  256.                         $dep_on_task = $row;
  257.                     }
  258.                 } else {
  259.                     log_info("this task is complete => don't check dependency");
  260.                 }
  261.                 $d++;
  262.             }
  263.         }       
  264.  
  265.         if($all_fixed) {
  266.             // this task depends only on fixated tasks
  267.             log_info("all dependencies are fixed");
  268.             fixate_task($i, $latest_end_date, $dep_on_task);
  269.         } else {
  270.             log_error("task has not fixed dependencies");
  271.         }
  272.  
  273.     } else {
  274.         // task has no dependencies
  275.         log_info("no dependencies => ");
  276.         fixate_task($i, time(), "");
  277.     }
  278.     log_info("</div><br />\n");    
  279. }
  280. ?>
  281.  
  282. <table name="table" cellspacing="1" cellpadding="1" border="0" width="100%">
  283. <tr>
  284.     <td><?php echo dPshowImage( dPfindImage( 'applet-48.png', $m ), 16, 16, '' ); ?></td>
  285.     <td nowrap><h1><?php echo $AppUI->_('Tasks Organizer Wizard'); ?></h1></td>
  286.     <td nowrap><img src="./images/shim.gif" width="16" height="16" alt="" border="0"></td>
  287.     <td valign="top" align="right" width="100%"></td>
  288. </tr>
  289. </table>
  290.  
  291. <?php
  292.  
  293. /*** Process updates ***/
  294.  
  295. // update tasks duration
  296. if($set_duration) {
  297.     foreach($set_duration as $key=>$val) {
  298.         if($val) {
  299.             $sql = "update tasks set task_duration=" . ($val * $dayhour[$key]) . " where task_id=" . $key;
  300.             mysql_query($sql);
  301.         }
  302.     }
  303.     $do = "ask"; // ask again
  304. }
  305.  
  306. if($set_dynamic) {
  307.     foreach($set_dynamic as $key=>$val) {
  308.         if($val) {
  309.             $sql = "update tasks set task_dynamic=1 where task_id=$key";
  310.             mysql_query($sql);
  311.         }
  312.     }
  313.     $do = "ask";
  314. }
  315.  
  316. ?>
  317.  
  318. <form name="form" method="post">
  319.  
  320. <?php
  321. if($do == "conf") {
  322.     echo '<table border="0" cellpadding="4" cellspacing="0" width="100%" class="tbl">';
  323.     echo '<tr>';
  324.     echo '<td>';
  325. }
  326.  
  327. function checkbox($name, $descr, $default = 0, $show = true) {
  328.     global $AppUI;
  329.     global $$name;
  330.     if(!isset($$name)) $$name=$default;
  331.     if($show) {
  332.         echo "<input type=checkbox name=$name value=1 " . ($$name?"checked":"") . ">".$AppUI->_($descr)."<br />";
  333.     } else {
  334.         echo "<input type=hidden name=$name value=" . ($$name?"1":"") . ">";
  335.     }
  336. }
  337.  
  338. // pull projects
  339. $sql = "SELECT project_id, project_name FROM projects ORDER BY project_name";
  340. $projects = arrayMerge( array( 0 => '(' . $AppUI->_('All') . ')' ), db_loadHashList( $sql ) );
  341.  
  342. echo $AppUI->_('Project').": " . arraySelect( $projects, 'project_id', 'class="text"', $project_id ) . "<br>";
  343.  
  344. checkbox("option_check_delayed_tasks", "Check delays for fixed tasks", 1, $do == "conf");
  345. checkbox("option_fix_task_group_date_ranges", "Fix date ranges for task groups according to subtasks dates", 1, $do == "conf");
  346. checkbox("option_no_end_date_warning", "Warn of fixed tasks without end dates", 0, $do == "conf");
  347. checkbox("option_advance_if_possible", "Begin new tasks if dependencies are finished before expected", 1, $do == "conf");
  348. checkbox("option_check_vital_users", "Allow two concurrent tasks when there are no vital users", 1, $do == "conf");
  349. checkbox("option_debug", "Show debug info", 0, $do == "conf");
  350.  
  351. if($do == "conf") { ?>
  352.     </td>
  353. </tr>
  354. </table>
  355. <br />
  356. <?php }
  357.  
  358. if($do != "conf") {
  359.     echo '<table border="0" cellpadding="4" cellspacing="0" width="100%" class="std">';
  360.     echo '<tr>';
  361.     echo '<td>';
  362.  
  363.     /**** Add tasks to an array and check conflicts ****/
  364.  
  365.     // Select tasks without children (sub tasks)
  366.  
  367.     $sql = "select a.*, !a.task_dynamic AS fixed FROM tasks AS a " .
  368.       "LEFT JOIN tasks AS b ON a.task_id = b.task_parent AND a.task_id != b.task_id " .
  369.       "WHERE (b.task_id IS NULL or b.task_id = b.task_parent) " .
  370.       "AND (a.task_project = $project_id) " .
  371.       "ORDER BY a.task_priority desc, a.task_order desc";    
  372.  
  373.     $dtrc = mysql_query( $sql );
  374.  
  375.     while ($row = mysql_fetch_array( $dtrc, MYSQL_ASSOC )) {
  376.         
  377.         // check durations
  378.         
  379.         // OBS: agregue if (task_end_date)
  380.  
  381.         if(!$row["task_duration"] && $row["task_end_date"] == $NO_DATE ) {
  382.             log_error("Task " .task_link($row) . " has no duration.",
  383.                 "Please enter the expected duration: "
  384.                 ."<input class=input type=text name='set_duration[" . $row["task_id"] . "]' size=3>"
  385.                 . "<select name='dayhour[" . $row["task_id"] . "]'>"
  386.                 . "<option value='1'>hour(s)</option>"
  387.                 . "<option value='24'>day(s)</option>"
  388.                 . "</select>"
  389.             );
  390.             $errors = true;
  391.         }
  392.         
  393.         // calculate or set blank task_end_date if unset
  394.  
  395.         if(!$row["task_dynamic"] && $row["task_end_date"] == $NO_DATE ) {
  396.             $end_date = new CDate( $row["task_start_date"] );
  397.             $durn = convert2days( $row["task_duration"], $row["task_duration_type"] );
  398.             $end_date->addDays( $durn );
  399.             $row["task_end_date"] = $end_date->getDate();
  400.             if($do=="ask" && $option_no_end_date_warning) {
  401.                 log_warning("Task " . task_link($row) . " has no end date. Using tasks duration instead.",
  402.                     "<input type=checkbox name='set_end_date[" . $row["task_id"] . "]' value=1> "
  403.                     ."Set end date to " . $row["task_end_date"]
  404.                 );
  405.             }
  406.         }
  407.  
  408.         // check delayed tasks
  409.         if($do == "ask") {
  410.             if(!$row["task_dynamic"] && $row["task_percent_complete"] == 0) {
  411.                 // nothing has be done yet
  412.                 $end_time = new CDate( db_dateTime2unix( $row["task_end_date"] ) );
  413.                 if($end_time < time()) {
  414.                     if($option_check_delayed_tasks) {
  415.                         log_warning("Task " .task_link($row) . " started on " . $row["task_start_date"] . " and ended on " . formatTime($end_time) . "." ,
  416.                             "<input type=checkbox name=set_dynamic[" . $row["task_id"] . "] value=1 checked> Set as dynamic task and reorganize<br />" .
  417.                             "<input type=checkbox name=set_priority[" . $row["task_id"] . "] value=1 checked> Set priority to high<br />"
  418.                         );
  419.                     }
  420.                 }
  421.             }
  422.         }
  423.         array_push($tasks, $row);
  424.     }
  425.     
  426.     if(!$errors) {
  427.         for($i = 0; $i < count($tasks) ; $i++) {
  428.             process_dependencies($i);
  429.         }
  430.     }    
  431.  
  432.     if($option_fix_task_group_date_ranges) {
  433.         // query taskgroups
  434.         $sql = "select distinct a.* from tasks as a, tasks as b " .
  435.           "WHERE (b.task_parent = a.task_id and a.task_id != b.task_id) " .
  436.           " AND (a.task_project = $project_id AND b.task_project = $project_id)";
  437.         $taskgroups = mysql_query($sql);
  438.         while($tg = mysql_fetch_array($taskgroups)) {
  439.             $children = get_last_children($tg);
  440.             $min_time = null;
  441.             $max_time = null;
  442.  
  443.             foreach($children as $child) {
  444.                 $start_time = db_dateTime2unix($child["task_start_date"]);
  445.                 $end_time = db_dateTime2unix($child["task_end_date"]);
  446.                 if (!$min_time || $start_time < $min_time) {
  447.                     $min_time = $start_time;
  448.                 }
  449.                 if (!$max_time || $end_time > $max_time) {
  450.                     $max_time = $end_time;
  451.                 }
  452.             }         
  453.  
  454.             if (db_dateTime2unix($tg["task_start_date"]) != $min_time
  455.                     || db_dateTime2unix($tg["task_end_date"]) != $max_time) {
  456.                 if ($do == "ask") {
  457.                     log_action("I will set date of task group " . task_link($tg) . " to " . formatTime($min_time) . " - " . formatTime($max_time) . ".");
  458.                 } else if ($do == "fixate") {
  459.                     log_action("Date range of task group " . task_link($tg) . " changed to " . formatTime($min_time) . " - " . formatTime($max_time) . ".");
  460.                     mysql_query("update tasks set task_start_date='" .  db_unix2dateTime($min_time) . "', task_end_date='" .  db_unix2dateTime($max_time) . "' where task_id=" . $tg["task_id"]);
  461.                 }
  462.             }
  463.         }
  464.     }
  465.     
  466.     if(!$action) {
  467.         echo "<font size=2><strong>".$AppUI->_('Tasks are already organized')."</strong></font><br />";
  468.     }
  469.  
  470.     echo '</td>';
  471.     echo '</tr>';
  472.     echo '</table>';
  473.     echo '<br />';
  474. }
  475.  
  476. if ($do=="conf" || $action) {
  477.     if(!$errors) {
  478.         echo "<input type=hidden name=do value=" . ($do=="ask"?"fixate":"ask") . ">";
  479.         if($do == "ask") {
  480.             echo "<font size=2><strong>".$AppUI->_('Do you want to accept this changes?')."</strong></font><br />";
  481.             echo "<input type=button value=accept class=button onClick='javascript:document.form.submit()'>";
  482.         } else if ($do == "fixate") {
  483.             echo "<font size=2><strong>".$AppUI->_('Tasks has been reorganized')."</strong></font><br />";
  484.         } else if ($do == "conf") {
  485.                 echo "<input type=button value=".$AppUI->_('start')." class=button onClick='javascript:document.form.submit()'>";
  486.         }
  487.     } else {
  488.         echo "<font size=2><strong>".$AppUI->_('Please correct the above errors')."</strong></font><br />";
  489.         echo "<input type=button value=submit class=button onClick='javascript:document.form.submit()'>";
  490.     }
  491. }
  492. ?>
  493.  
  494. </form>
  495.  
  496. </body>
  497. </html>
  498.  
  499.