schedulator: only do weekdays, not weekends.
[wvapps.git] / gracefultavi / parse / schedulator.php
blob549ef4e1b177d88c73ead43f091d03b979263d9a
1 <?php
3 global $sch_user;
4 global $sch_start;
5 global $sch_load;
6 global $sch_bugs;
7 global $sch_curday;
8 global $sch_got_bug;
9 global $sch_unknown_fixfor;
10 global $bug_h;
12 function bug_person($user)
14 global $bug_h;
15 bug_init();
17 $query = "select ixPerson from Person where sEmail like '$user@%'";
18 $result = mysql_query($query, $bug_h);
19 $row = mysql_fetch_row($result);
20 if ($row)
21 return $row[0];
22 else
23 return -1;
26 function bug_duedate($fixfor)
28 global $bug_h;
29 bug_init();
31 $query = "select dt from FixFor where sFixFor='$fixfor'";
32 $result = mysql_query($query, $bug_h);
33 $row = mysql_fetch_row($result);
34 if ($row)
35 return $row[0];
36 else
37 return '';
40 function bug_list($user, $fixfor, $startdate, $enddate)
42 global $bug_h;
43 bug_init();
45 $ffquery = '';
46 $userquery = '';
47 $endquery = '';
49 if ($fixfor and $enddate)
50 $ffquery = " and (f.sFixFor = '$fixfor' or " .
51 "(f.dt is not null and f.dt <= '$enddate')) ";
52 else if ($fixfor)
53 $ffquery = " and f.sFixFor = '$fixfor' ";
55 # any bug opened before the end date counts toward that deadline, even
56 # if the deadline has passed and the bug isn't done.
57 if ($enddate)
58 $endquery = " and b.dtOpened < '$enddate' ";
60 # We want to get all bugs resolved by the user after the given start
61 # date, *and* all bugs currently assigned to the user.
62 if ($user)
64 $personid = bug_person($user);
65 $userquery = " and (" .
66 " (b.ixPersonAssignedTo=$personid and sStatus='ACTIVE') " .
67 " or (sStatus<>'ACTIVE' and e.ixPerson=$personid " .
68 " and e.sVerb like 'RESOLVED%' " .
69 " and e.dt >= '$startdate')" .
70 ") ";
73 $query = "select distinct " .
74 "b.ixBug,sStatus,sTitle,hrsOrigEst,hrsCurrEst,hrsElapsed, " .
75 " ifnull(f.dt, '2099/9/9') as sortdate " .
76 "from Bug as b, BugEvent as e, FixFor as f, Status as s " .
77 "where e.ixBug=b.ixBug " .
78 " and f.ixFixFor=b.ixFixFor " .
79 " and s.ixStatus=b.ixStatus " .
80 $endquery .
81 $userquery .
82 $ffquery .
83 " order by sortdate, sFixFor, ixPriority, ixBug ";
84 #print "(($query))<p>";
85 $result = mysql_query($query, $bug_h);
86 if (!$result)
87 print mysql_error($bug_h);
89 $a = array();
90 while ($row = mysql_fetch_row($result))
92 $bug = array_shift($row);
93 $status = array_shift($row);
94 if ($status != "ACTIVE")
96 # the bug is done, so don't give it any more time remaining
97 if (!$row[2])
98 $row[2] = 0.01; # nonzero so we know the 'remaining' is accurate
99 $row[3] = $row[2]; # elapsed = estimate; bug is done!
101 $a[$bug] = $row;
104 return $a;
107 function bug_get($bugid)
109 global $bug_h;
111 bug_init();
112 $result = mysql_query("select sTitle,hrsOrigEst,hrsCurrEst,hrsElapsed, " .
113 " sStatus,sFixFor " .
114 "from Bug as b, Status as s, FixFor as f " .
115 "where s.ixStatus=b.ixStatus " .
116 " and f.ixFixFor=b.ixFixFor " .
117 " and ixBug=" . ($bugid+0),
118 $bug_h);
119 $row = mysql_fetch_row($result);
121 if (!$row)
122 return array($bugid, 0, 0, 0);
123 else
124 return $row;
127 function bug_link($bugid)
129 return "<a href='http://nits/FogBUGZ3/?$bugid'>$bugid</a>";
132 function bug_title($bugid)
134 $bug = bug_get($bugid);
135 return $bug[0];
138 function bug_set_release($fixfor, $dt)
140 global $bug_h;
141 bug_init();
143 $query = "delete from schedulator.Milestone " .
144 " where sMilestone='$fixfor' and nSub=0";
145 $result = mysql_query($query, $bug_h);
147 $query = "insert into schedulator.Milestone " .
148 " (sMilestone, nSub, dtDue) " .
149 " values ('$fixfor', 0, '$dt')";
150 $result = mysql_query($query, $bug_h);
153 function bug_set_milestones($fixfor, $dates)
155 global $bug_h;
156 bug_init();
158 $query = "delete from schedulator.Milestone " .
159 " where sMilestone='$fixfor' and nSub>0";
160 $result = mysql_query($query, $bug_h);
161 if (!$result)
162 print mysql_error($bug_h);
164 $n = 1;
165 foreach ($dates as $d)
167 $query = "insert into schedulator.Milestone " .
168 " (sMilestone, nSub, dtDue) " .
169 " values ('$fixfor', $n, '$d')";
170 $result = mysql_query($query, $bug_h);
171 if (!$result)
172 print mysql_error($bug_h);
173 $n++;
177 function bug_get_milestones($fixfor)
179 global $bug_h;
180 bug_init();
182 $query = "select distinct dtDue from schedulator.Milestone " .
183 " where sMilestone='$fixfor' and nSub>0 order by dtDue";
184 $result = mysql_query($query, $bug_h);
185 if (!$result)
186 print mysql_error($bug_h);
188 $a = array();
189 while ($row = mysql_fetch_row($result))
190 array_push($a, $row[0]);
191 return $a;
194 function bug_add_task($user, $fixfor, $t)
196 global $bug_h;
197 bug_init();
199 $task = mysql_escape_string($t[0]);
200 $subtask = mysql_escape_string($t[1]);
201 $query = "insert into schedulator.Task " .
202 " (sPerson, sFixFor, " .
203 " sTask, sSubTask, " .
204 " hrsOrigEst, hrsCurrEst, hrsElapsed, dtDue, fDone) " .
205 " values ('$user', '$fixfor', \"$task\", \"$subtask\", " .
206 " $t[2], $t[3], $t[4], '$t[5]', $t[6])";
207 $result = mysql_query($query, $bug_h);
208 if (!$result)
209 print 'x-' . mysql_error($bug_h);
212 function bug_add_tasks($user, $fixfor, $tasks)
214 foreach ($tasks as $t)
215 bug_add_task($user, $fixfor, $t);
218 function bug_start_user($user)
220 global $bug_h;
221 bug_init();
223 $query = "update schedulator.Task set fValid=0 where sPerson='$user'";
224 $result = mysql_query($query, $bug_h);
227 function bug_finish_user($user)
229 global $bug_h;
230 bug_init();
232 $query = "delete from schedulator.Task where fValid=0";
233 $result = mysql_query($query, $bug_h);
237 function sch_today()
239 # we mostly use GMT for our work, but if they want today, they want
240 # the local "today"
241 $dat = strftime("%Y/%m/%d", time());
242 $today = sch_parse_day($dat);
243 #print("(today=$dat:$today/" . sch_format_day($today) . ")");
244 return $today;
247 function sch_parse_day($day)
249 if ($day == '')
250 return 0;
251 else if (preg_match(",(....)[/-](..)[/-](..),", $day, $a))
253 $year = $a[1];
254 $month = $a[2];
255 $day = $a[3];
257 $stamp = mktime(0,0,12, $month, $day, $year);
258 $spl = localtime($stamp);
259 #print("(day-$year/$month/$day:$stamp:$spl[6])");
260 if ($spl[6] == 0) # sunday
261 $stamp += 24*60*60;
262 else if ($spl[6] == 6) # saturday
263 $stamp += 2*24*60*60;
265 $stamp -= 4*24*60*60; # the epoch was a Thursday. Skip to Monday.
266 $days = floor($stamp/24/60/60);
267 $weeks = floor($days/7);
268 $days -= $weeks*7;
269 #printf("(days/weeks:$days/$weeks)");
270 return $weeks*5 + $days;
272 else
274 print("(INVALID DATE:'$day')");
275 return 0;
279 function sch_format_day($day)
281 if (!$day)
282 return '';
284 # convert working days to "real" days
285 $weeks = floor($day/5);
286 $days = $day - $weeks*5;
287 $frac = $days - floor($days);
288 $stamp = ($weeks*7 + $days) * 24*60*60;
289 $stamp += 4*24*60*60; # the epoch was a Thursday
290 $stamp += 12*60*60; # php timezone handling is insane
292 return strftime("%Y/%m/%d", $stamp);# . sprintf("+%.1f", $frac);
295 function sch_add_hours($day, $hours)
297 global $sch_load;
299 if ($hours < 0)
300 return $day; # never subtract hours from the date!
302 # FIXME: deal with "working days" vs. weekends
303 return $day + ($hours * $sch_load) / 8.0;
306 # returns a time in hours
307 function sch_parse_period($str)
309 #return "($str)";
310 if (preg_match('/([0-9.]+) *(h|hr|hrs|hour|hours)$/', $str, $out))
311 return $out[1]+0.0;
312 else if (preg_match('/([0-9.]+) *(d|day|days)$/', $str, $out))
313 return $out[1] * 8.0;
314 else if (preg_match('/([0-9.]+) *(min|minutes)$/', $str, $out))
315 return $out[1] / 60.0;
316 else
317 return $str + 0.0;
320 function _sch_period($hours)
322 #return $hours;
323 if (!$hours)
324 return "";
325 else if ($hours < 9)
326 return sprintf("%dh", $hours);
327 else if ($hours < 10*8.0)
328 return sprintf("%.1fd", $hours/8);
329 else
330 return sprintf("%dd", $hours/8);
333 function sch_period($hours)
335 if ($hours < -1)
336 return "<font color=red>-" . _sch_period(-$hours) . "</font>";
337 else
338 return _sch_period($hours);
341 function sch_fullline($text)
343 return "<tr><td colspan=7>$text</td></tr>";
346 function sch_genline($feat, $task, $orig, $curr, $elapsed, $left, $due)
348 $ret = "<tr>";
350 $junk1 = $junk2 = '';
351 if ($left == "done")
353 $junk1 = "<strike><font color=grey><i>";
354 $junk2 = "</i></font></strike>";
357 if ($task)
358 $ret .= "<td>$junk1$feat$junk2</td><td>$junk1$task$junk2</td>";
359 else
360 $ret .= "<td colspan=2>$junk1$feat$junk2</td>";
361 return $ret . "<td>$junk1" .
362 join("$junk2</td><td>$junk1",
363 array($orig, $curr, $elapsed, $left, $due)) .
364 "$junk2</td></tr>";
367 function sch_line($feat, $task, $orig, $curr, $elapsed, $remain, $done)
369 global $sch_curday, $sch_bugs, $sch_got_bug;
371 if ($done)
372 $sremain = 'done';
373 else if (!$remain)
374 $sremain = '';
375 else
376 $sremain = sch_period($remain);
378 $sch_curday = sch_add_hours($sch_curday, $curr);
379 $due = sch_format_day($sch_curday);
380 if ((!$curr || $remain) && !$done
381 && floor($sch_curday) < floor(sch_today()))
382 $due = "<font color=red>$due</font>";
384 return sch_genline($feat, $task,
385 sch_period($orig), sch_period($curr),
386 sch_period($elapsed), $sremain,
387 $due);
390 function sch_bug($feat, $task, $_orig, $_curr, $_elapsed, $done)
392 global $sch_user, $sch_curday, $sch_bugs, $sch_need_extraline;
393 global $sch_got_bug, $sch_unknown_fixfor;
395 if ($sch_need_extraline)
397 $ret .= sch_fullline('&nbsp;');
398 $sch_need_extraline = 0;
401 $xfeat = $feat;
402 $fixfor = '';
403 if (preg_match('/^[0-9]+$/', $feat))
405 $sch_got_bug[$feat] = 1;
406 $bug = bug_get($feat);
407 if (!$task) $task = $bug[0];
408 if (!$_orig) $_orig = $bug[1];
409 if (!$_curr) $_curr = $bug[2];
410 if (!$_elapsed) $_elapsed = $bug[3];
411 if (!$done && $bug[4] != 'ACTIVE') $done = 1;
412 $fixfor = $bug[5];
413 $xfeat = bug_link($feat);
416 $orig = sch_parse_period($_orig);
417 $curr = sch_parse_period($_curr);
418 $elapsed = sch_parse_period($_elapsed);
420 $remain = $curr - $elapsed;
422 if (!$remain && $curr)
423 $done = 1;
425 $ret .= sch_line($xfeat, $task, $orig, $curr, $elapsed, $remain, $done);
426 $buga = array($feat, $task, $orig, $curr, $elapsed,
427 sch_format_day($sch_curday), $done);
428 if ($fixfor)
429 bug_add_task($sch_user, $fixfor, $buga);
430 else
431 array_push($sch_unknown_fixfor, $buga);
432 return $ret;
435 function sch_extrabugs($user, $fixfor, $enddate)
437 global $sch_got_bug, $sch_start;
439 $start = sch_format_day($sch_start);
441 $a = bug_list($user, $fixfor, $start, $enddate);
442 foreach ($a as $bugid => $bug)
444 if (!$sch_got_bug[$bugid])
445 $ret .= sch_bug($bugid, $bug[0], $bug[1], $bug[2], $bug[3], 0);
447 return $ret;
450 function sch_milestone($descr, $name, $due)
452 global $sch_user, $sch_load, $sch_curday, $sch_need_extraline;
454 # if no due date was given, assume it's the current date for purposes
455 # of collecting extra bugs.
456 if (!$due)
457 $tmpdue = sch_format_day($sch_curday);
458 else
459 $tmpdue = $due;
460 $ret .= sch_extrabugs($sch_user, $name, $tmpdue);
462 # if no due date was given, make it the day after the last bug finished.
463 if (!$due)
464 $due = sch_format_day($sch_curday+1);
466 $old_curday = $sch_curday;
468 $newday = sch_parse_day($due);
469 $xdue = sch_format_day($newday);
470 $slip = ($newday-$sch_curday)*8 / $sch_load;
471 #$ret .= sch_line("SLIPPAGE (to $xdue)", "", 0,$slip,0,$slip, 0);
472 $ret .= sch_line("<b>$descr: $name ($xdue)</b>", '', 0,$slip,0,$slip, 0);
473 $sch_need_extraline = 1;
475 $sch_curday = $old_curday; # slippage doesn't actually take time... right?
477 return $ret;
480 function view_macro_schedulator($text)
482 global $sch_start, $sch_curday, $sch_user, $sch_load, $sch_got_bug;
483 global $sch_unknown_fixfor;
485 $ret = "";
487 if (!preg_match_all('/"[^"]*"|[^ ]+/', $text, $words))
488 return "regmatch failed!\n";
489 $words = $words[0]; # don't know why I have to do this, exactly...
491 foreach ($words as $key => $value)
493 if (preg_match('/^"(.*)"$/', $value, $result))
494 $words[$key] = $result[1];
497 if (0)
499 $ret .= "Hello: ";
500 foreach ($words as $word)
501 $ret .= "($word) ";
502 $ret .= "<br>\n";
505 if ($words[0] == "START")
507 $ret .= "<table border=0 width='95%'>\n";
508 $ret .= "<tr><th>" .
509 join("</th><th>", array("Task", "Subtask", "Orig", "Curr",
510 "Done", "Left", "Due")) .
511 "</th></tr>\n";
512 $sch_user = $words[1];
513 $sch_start = $sch_curday = sch_parse_day($words[2]);
514 $sch_unknown_fixfor = array();
515 $sch_load = 1.0;
516 $ret .= sch_line("START", "", 0,0,0,0, 0);
517 bug_start_user($sch_user);
519 else if ($words[0] == "LOADFACTOR")
521 $loadtmp = $words[1] + 0.0;
522 if ($loadtmp < 1.0)
523 $ret .= "(INVALID LOAD FACTOR:'$words[1]')";
524 else
525 $sch_load = $loadtmp;
527 $ret .= sch_fullline("(Load factor = $sch_load)");
529 else if ($words[0] == "FIXFOR")
531 $ret .= sch_extrabugs($sch_user, $words[1], '');
533 else if ($words[0] == "SETBOUNCE")
535 $cmd = array_shift($words);
536 $msname = array_shift($words);
537 bug_set_milestones($msname, $words);
539 else if ($words[0] == "MILESTONE" || $words[0] == "RELEASE"
540 || $words[0] == "BOUNCE")
542 $msname = $words[1];
543 $msdue = $words[2];
545 $extra_due = bug_get_milestones($msname);
546 foreach ($extra_due as $xdue)
547 $ret .= sch_milestone("ZeroBugBounce", $msname, $xdue);
549 if (!$msdue)
550 $msdue = bug_duedate($msname);
551 $ret .= sch_milestone($words[0], $msname, $msdue);
552 bug_set_release($msname, $msdue);
553 bug_add_tasks($sch_user, $msname, $sch_unknown_fixfor);
554 $sch_unknown_fixfor = array();
556 else if ($words[0] == "END")
558 $ret .= sch_extrabugs($sch_user, '', '');
559 bug_add_tasks($sch_user, 'UNKNOWN', $sch_unknown_fixfor);
560 bug_finish_user($sch_user);
561 $ret .= sch_line("END", "", 0,0,0,0, 0);
562 $ret .= "</table>";
564 else
566 $bug = $words[0];
567 $task = $words[1];
568 $est = $words[2];
569 $elapsed = $words[3];
571 $ret .= sch_bug($bug, $task, $est, $est, $elapsed, 0);
574 return $ret;